{"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"]}},"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":"Сессии и активность","categoryId":"sessions","order":1,"concepts":[{"id":"active-sessions","title":"Показать активные сессии / выполняющиеся запросы","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 показывает текущие активные сессии. Для полного текста SQL выполните соединение с v$sql по sql_id.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, state, wait_event_type, query\nFROM pg_stat_activity\nWHERE state <> 'idle';","note":"state = 'active' показывает сессии, выполняющие запрос в данный момент. Длительность вычисляйте по 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":"В MySQL 8 / MariaDB performance_schema.processlist удерживает меньше блокировок.","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":"Для быстрого просмотра: EXEC sp_who2 'active';","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"active\": true })","note":"Фильтр долгих операций: db.currentOp({ active: true, secs_running: { $gt: 5 } })","danger":"safe"},"redis":{"command":"CLIENT LIST\n# For a summary: INFO clients","note":"Redis однопоточный, поэтому вместо «выполняющихся запросов» выводятся подключённые клиенты. Для медленных команд используйте SLOWLOG GET.","danger":"safe"}}},{"id":"blocking-sessions","title":"Найти блокирующие / ожидающие блокировку сессии","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":"Сессия с заполненным blocking_session заблокирована сессией с SID, указанным в значении blocking_session.","danger":"safe"},"postgres":{"command":"SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by,\n       wait_event, query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) > 0;","note":"Массив blocked_by содержит PID, блокирующие данный pid.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.innodb_lock_waits\\G\n-- MySQL 8: SELECT * FROM performance_schema.data_lock_waits;","note":"Схема sys показывает ожидающие и блокирующие trx_id / потоки в удобочитаемом виде.","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":"Запросы с blocking_session_id <> 0 заблокированы. Для прослеживания цепочки можно построить рекурсивный запрос.","danger":"safe"},"mongodb":{"command":"db.currentOp({ \"waitingForLock\": true })","note":"WiredTiger блокирует на уровне документа/коллекции. Долгие операции можно завершить через 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":"Блокировка обычно вызвана одной долго выполняющейся командой (напр. KEYS *, большой SORT); SLOWLOG их показывает.","danger":"safe"}}},{"id":"kill-session","title":"Завершить (kill) сессию / запрос","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":"Возьмите значения sid и serial# из v$session. IMMEDIATE сразу запускает откат.","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":"Сначала попробуйте pg_cancel_backend; если не помогает, используйте pg_terminate_backend.","danger":"danger"},"mysql":{"command":"-- Kill only the query:\nKILL QUERY <id>;\n-- Close the connection entirely:\nKILL CONNECTION <id>;","note":"Значение <id> — это столбец Id в processlist.","danger":"danger"},"mssql":{"command":"KILL <session_id>;\n-- To see rollback progress:\n-- KILL <session_id> WITH STATUSONLY;","note":"KILL откатывает открытую транзакцию; для больших транзакций откат может занять много времени.","danger":"danger"},"mongodb":{"command":"db.killOp(<opid>)","note":"Возьмите значение opid из поля opid в выводе db.currentOp().","danger":"danger"},"redis":{"command":"CLIENT KILL ID <client-id>\n# or by address: CLIENT KILL ADDR 10.0.0.5:6379","note":"client-id получите через CLIENT LIST. Уже выполняющуюся одиночную команду прервать нельзя (кроме Lua-скрипта: SCRIPT KILL); нужно дождаться её завершения. Чтобы освободить клиента, ожидающего на блокирующей команде вроде BLPOP/XREAD BLOCK, не разрывая его соединение, используйте CLIENT UNBLOCK <client-id> [TIMEOUT|ERROR]. CLIENT UNPAUSE лишь возобновляет клиентов, приостановленных через CLIENT PAUSE.","danger":"caution"}}},{"id":"connection-count","title":"Число подключений и лимит подключений","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":"Лимиты задаются параметрами processes/sessions. Использование можно также проверить в представлении resource_limit.","danger":"safe"},"postgres":{"command":"SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';\nSHOW max_connections;","note":"pg_stat_activity включает и фоновые процессы, поэтому считаются только клиентские подключения (backend_type = 'client backend'). Слоты superuser_reserved_connections зарезервированы для администраторов.","danger":"safe"},"mysql":{"command":"SHOW STATUS LIKE 'Threads_connected';\nSHOW VARIABLES LIKE 'max_connections';","note":"Статус Max_used_connections показывает пиковое значение.","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 — теоретический потолок; практический лимит зависит от памяти и рабочих потоков.","danger":"safe"},"mongodb":{"command":"db.serverStatus().connections","note":"Возвращает поля current, available и totalCreated. Лимит задаётся net.maxIncomingConnections.","danger":"safe"},"redis":{"command":"INFO clients\nCONFIG GET maxclients","note":"connected_clients — текущее число; maxclients — настроенный потолок (по умолчанию 10000).","danger":"safe"}}}]},"rosetta/storage.json":{"category":"Пространство и хранение","categoryId":"storage","order":2,"concepts":[{"id":"database-size","title":"Показать общий размер базы данных","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":"Только для занятых сегментов суммируйте dba_segments. Для TEMP добавьте dba_temp_files.","danger":"safe"},"postgres":{"command":"SELECT pg_size_pretty(pg_database_size(current_database()));","note":"Для всех баз данных: 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":"Для InnoDB это значение оценочное; точный размер файлов проверяйте по ibdata/файлам на уровне ОС.","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":"Столбец size — в страницах по 8 KB; * 8 / 1024 переводит в MB.","danger":"safe"},"mongodb":{"command":"db.stats(1024*1024)\n// returns the dataSize, storageSize, indexSize fields in MB","note":"storageSize — сжатый размер на диске; dataSize — несжатый логический размер.","danger":"safe"},"redis":{"command":"INFO memory\n# the used_memory_human line gives the total size in memory\nDBSIZE  # number of keys","note":"Redis работает в памяти; «размер» означает использование RAM. Размер файла RDB/AOF — это сохранённое состояние на диске.","danger":"safe"}}},{"id":"largest-tables","title":"Самые большие таблицы / коллекции","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":"Уберите фильтр segment_type, чтобы видеть и индексы.","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 — сумма таблицы + индексов + TOAST; для одной таблицы используйте pg_relation_size.","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":"Для конкретной схемы используйте 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 охватывает все зарезервированные страницы (данные + индексы).","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 — сжатый размер на диске. Стоимость stats() низкая даже на больших коллекциях.","danger":"safe"},"redis":{"command":"redis-cli --bigkeys\n# To rank by memory (bytes): redis-cli --memkeys","note":"Оба сканируют всё пространство ключей через SCAN (сервер не блокируют, но добавляют нагрузку); разница в метрике: --bigkeys показывает самые большие ключи по числу элементов/длине для каждого типа, --memkeys — по потреблению памяти через MEMORY USAGE. Для одного ключа используйте MEMORY USAGE <key>.","danger":"caution"}}},{"id":"free-space","title":"Проверить свободное место / процент заполнения","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":"Для файлов с AUTOEXTEND on реальный предел — maxbytes; для MAXSIZE проверьте dba_data_files.maxbytes.","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 получает от ОС; для критических порогов обязателен мониторинг ОС (df).","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 — освобождаемое (фрагментированное) пространство внутри таблицы; свободное место на диске — на уровне ОС.","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 — в страницах; деление на 128 даёт MB (страницы по 8 KB).","danger":"safe"},"mongodb":{"command":"var s = db.stats();\n({ fsTotalGB: +(s.fsTotalSize/1073741824).toFixed(1),\n   fsUsedGB:  +(s.fsUsedSize/1073741824).toFixed(1) })","note":"В WiredTiger fsTotalSize / fsUsedSize показывают общий и занятый объём диска на уровне ОС.","danger":"safe"},"redis":{"command":"INFO memory\nCONFIG GET maxmemory\n# Usage = used_memory / maxmemory","note":"Если maxmemory равен 0, лимита нет, и при заполнении RAM вмешивается OOM killer ОС. maxmemory-policy определяет поведение вытеснения.","danger":"safe"}}},{"id":"row-count","title":"Получить число строк / документов (быстро)","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 отражает последний запуск ANALYZE/DBMS_STATS; для точного значения используйте COUNT(*).","danger":"safe"},"postgres":{"command":"-- Estimated (instant):\nSELECT reltuples::bigint FROM pg_class WHERE relname = 'customer';\n-- Exact: SELECT count(*) FROM customer;","note":"reltuples — оценка на момент последнего ANALYZE/VACUUM; полный COUNT на больших таблицах дорог.","danger":"safe"},"mysql":{"command":"-- Estimated (InnoDB):\nSELECT table_rows FROM information_schema.tables\nWHERE table_name = 'customer';\n-- Exact: SELECT COUNT(*) FROM customer;","note":"В InnoDB table_rows — оценка и может отличаться до 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 (куча) или 1 (кластерный) даёт число строк; намного быстрее сканирования COUNT(*).","danger":"safe"},"mongodb":{"command":"// Fast (from metadata):\ndb.customer.estimatedDocumentCount()\n// Exact (can count with a filter): db.customer.countDocuments({})","note":"estimatedDocumentCount читает метаданные (мгновенно); countDocuments выполняет реальное сканирование.","danger":"safe"},"redis":{"command":"DBSIZE\n# Specific pattern: count roughly with SCAN (do not use KEYS * in production)","note":"DBSIZE возвращает общее число ключей в выбранной базе за O(1).","danger":"safe"}}}]},"rosetta/performance.json":{"category":"Производительность","categoryId":"performance","order":3,"concepts":[{"id":"top-slow-queries","title":"Найти самые медленные / дорогие запросы","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":"Для исторического анализа используйте AWR: dba_hist_sqlstat. По sql_id план показывает DBMS_XPLAN.DISPLAY_CURSOR.","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":"Требуется расширение pg_stat_statements (shared_preload_libraries). Сброс — 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":"Альтернатива: журнал медленных запросов с slow_query_log = ON и 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":"Если Query Store включён, для более полного исторического анализа используйте sys.query_store_runtime_stats.","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":"Профилировщик добавляет накладные расходы на запись; после диагностики отключите его через db.setProfilingLevel(0).","danger":"caution"},"redis":{"command":"SLOWLOG GET 10\n# Set the threshold (microseconds): CONFIG SET slowlog-log-slower-than 10000","note":"slowlog-log-slower-than задаётся в микросекундах (10000 = 10 ms). Очистка — SLOWLOG RESET.","danger":"safe"}}},{"id":"explain-plan","title":"Получить план выполнения запроса (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 даёт оценочный план; для фактического числа строк используйте DISPLAY_CURSOR + STATISTICS_LEVEL=ALL.","danger":"safe"},"postgres":{"command":"EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT * FROM customer WHERE city = 'Ankara';","note":"ANALYZE ДЕЙСТВИТЕЛЬНО выполняет запрос (осторожно с INSERT/UPDATE/DELETE; пробуйте внутри транзакции и делайте 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+) выполняет запрос; FORMAT=JSON даёт подробности без выполнения.","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":"В SSMS «Include Actual Execution Plan» (Ctrl+M) показывает графический план.","danger":"caution"},"mongodb":{"command":"db.customer.find({ city: \"Ankara\" }).explain(\"executionStats\")","note":"executionStats показывает фактическое число возвращённых документов и использование индексов (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":"Производительность определяется сложностью команд и размером ключей/структур данных; измеряйте её через SLOWLOG.","danger":"safe"}}},{"id":"unused-indexes","title":"Найти неиспользуемые индексы","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":"Если USED = NO, индекс не использовался в период мониторинга. Отключите мониторинг через NOMONITORING USAGE. Старое v$object_usage показывает только индексы пользователя сессии и устарело начиная с 12c. В 12.2+ использование индексов отслеживается автоматически в 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 означает, что индекс не использовался с последнего сброса статистики; не удаляйте индексы UNIQUE/PK.","danger":"safe"},"mysql":{"command":"SELECT * FROM sys.schema_unused_indexes;","note":"performance_schema должна быть включена, а сервер должен проработать некоторое время; ограничения UNIQUE исключены.","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 сбрасывается при перезапуске сервера; выдерживайте достаточный период наблюдения.","danger":"safe"},"mongodb":{"command":"db.customer.aggregate([{ $indexStats: {} }])\n// Indexes with low/0 accesses.ops are candidates","note":"accesses.ops считает обращения с момента accesses.since; индекс _id удалить нельзя.","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":"Модель данных — ключ-значение, поэтому традиционного понятия индекса нет.","danger":"safe"}}},{"id":"cache-hit-ratio","title":"Измерить коэффициент попаданий в кэш","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":"Низкий коэффициент не всегда плох; интерпретируйте его в контексте нагрузки, а не изолированно.","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%+. Если ниже, пересмотрите shared_buffers и кэш ОС.","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 — чтения с диска, read_requests — все чтения. Если коэффициент низкий, увеличьте 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":"В современных SQL Server Page Life Expectancy (PLE) — более показательный индикатор нехватки памяти.","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":"Высокие значения «pages read into cache» и вытеснения указывают, что кэш WiredTiger слишком мал.","danger":"safe"},"redis":{"command":"INFO stats\n# keyspace_hits / (keyspace_hits + keyspace_misses) = hit ratio","note":"Устойчиво низкий коэффициент попаданий говорит о том, что нужно пересмотреть настройки TTL/вытеснения или паттерн доступа.","danger":"safe"}}}]},"rosetta/backup.json":{"category":"Бэкап и восстановление","categoryId":"backup","order":4,"concepts":[{"id":"logical-dump","title":"Логический бэкап (дамп)","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":"Использует Data Pump. Объект DIRECTORY должен быть заранее создан через CREATE DIRECTORY. Для физических бэкапов используйте RMAN.","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 позволяет гибкое восстановление (выборочные таблицы/параллельно). Для всего кластера (включая роли) используйте pg_dumpall.","danger":"safe"},"mysql":{"command":"mysqldump -u root -p --single-transaction \\\n  --routines --triggers --events mydb > db.sql","note":"--single-transaction даёт согласованный бэкап без блокировок в InnoDB. Параллельные варианты для больших данных: mydumper/myloader или MySQL Shell util.dumpInstance()/dumpSchemas() (mysqlpump объявлен устаревшим в 8.0.34 и удалён в 8.4).","danger":"safe"},"mssql":{"command":"BACKUP DATABASE [MyDB]\nTO DISK = N'C:\\backup\\MyDB_full.bak'\nWITH COMPRESSION, CHECKSUM, INIT;","note":"INIT перезаписывает существующий файл; для дописывания используйте NOINIT. Для бэкапа журнала используйте BACKUP LOG (в модели восстановления FULL).","danger":"safe"},"mongodb":{"command":"mongodump --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --db=mydb","note":"В шардированном кластере для согласованности делайте его через mongos при остановленном балансировщике.","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 работает в фоне через fork; SAVE блокирует сервер (не используйте в продакшене). При включённом AOF теряется меньше данных.","danger":"safe"}}},{"id":"restore-dump","title":"Восстановить из бэкапа","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":"Используйте REMAP_SCHEMA для загрузки в другую схему и REMAP_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 удаляет существующие объекты. Для параллельного восстановления используйте -j <n>.","danger":"danger"},"mysql":{"command":"mysql -u root -p target_db < db.sql","note":"Целевая база данных должна уже существовать (CREATE DATABASE). Для больших файлов предпочтительна команда source.","danger":"danger"},"mssql":{"command":"RESTORE DATABASE [MyDB]\nFROM DISK = N'C:\\backup\\MyDB_full.bak'\nWITH REPLACE, RECOVERY;","note":"Для восстановления на момент времени восстановите полный бэкап WITH NORECOVERY, затем примените бэкапы журнала с STOPAT.","danger":"danger"},"mongodb":{"command":"mongorestore --uri=\"mongodb://localhost:27017\" \\\n  --archive=db.archive.gz --gzip --drop","note":"--drop удаляет целевые коллекции перед восстановлением. Для выборочного восстановления используйте --nsInclude.","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":"При включённом AOF вместо RDB загружается AOF; чтобы восстановиться из RDB, запустите с appendonly no или пересоздайте AOF.","danger":"danger"}}},{"id":"last-backup-status","title":"Статус / время последнего бэкапа","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 можно также посмотреть через 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":"Логические бэкапы (pg_dump) не регистрируются внутри БД; проверяйте журналы инструмента/задания бэкапа.","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 показывает доступные файлы binlog (окно PITR); история бэкапов — в журнале инструмента (Xtrabackup и т. п.).","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 хранит каждый бэкап, сделанный 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":"Окно oplog определяет, насколько далеко назад возможен PITR из набора реплик; история бэкапов — в журнале инструмента.","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 даёт время последнего успешного сохранения RDB (Unix epoch); rdb_changes_since_last_save показывает, сколько записей ещё не сохранено в бэкап.","danger":"safe"}}}]},"rosetta/security.json":{"category":"Пользователи и привилегии","categoryId":"security","order":5,"concepts":[{"id":"list-users","title":"Список пользователей / ролей","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 показывает состояния LOCKED/EXPIRED. Для ролей используйте dba_roles.","danger":"safe"},"postgres":{"command":"SELECT rolname, rolsuper, rolcanlogin, rolvaliduntil\nFROM pg_roles\nORDER BY rolname;\n-- Shortcut (psql): \\du","note":"В PostgreSQL пользователь — это роль с атрибутом LOGIN; их различает rolcanlogin.","danger":"safe"},"mysql":{"command":"SELECT user, host, account_locked, password_expired\nFROM mysql.user\nORDER BY user;","note":"В MySQL учётная запись — это пара пользователь + хост ('app'@'%' и 'app'@'localhost' — разные).","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":"Login — объект уровня сервера, user — уровня базы данных; они сопоставляются по 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":"Пользователи определены в базе, где были созданы (authenticationDatabase).","danger":"safe"},"redis":{"command":"ACL LIST\n# Names only: ACL USERS\n# Your own identity: ACL WHOAMI","note":"Система ACL в Redis 6+. До неё был единственный пользователь 'default' и requirepass.","danger":"safe"}}},{"id":"create-user-grant","title":"Создать пользователя и выдать привилегии","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":"Без CREATE SESSION пользователь не сможет подключиться. Для квоты: 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":"Для таблиц, которые будут созданы в будущем: 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":"'%' разрешает подключения с любого хоста; для безопасности по возможности указывайте конкретный IP/хост. CREATE USER/GRANT действуют сразу; FLUSH PRIVILEGES нужен, только если DML выполнялся напрямую над таблицами привилегий.","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":"Login действует на весь сервер; user создаётся отдельно в каждой базе. Для роли: 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":"База, в которой создан пользователь, становится его auth db. Встроенные роли: 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 = включён, >password добавляет пароль, ~app:* — шаблон ключей, +@read/+@write — категории команд. Без сохранения теряется при перезапуске: ACL SAVE работает, только если настроен aclfile, иначе возвращает ошибку; в этом случае используйте CONFIG REWRITE.","danger":"caution"}}},{"id":"show-privileges","title":"Показать привилегии пользователя","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":"grantee хранится в верхнем регистре. Для косвенных привилегий, выданных через роли, обходите также 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":"Членство в ролях (наследование) хранится в pg_auth_members; \\du/\\drg показывают только прямое членство, для косвенного используйте pg_has_role('app', '<role>', 'MEMBER').","danger":"safe"},"mysql":{"command":"SHOW GRANTS FOR 'app'@'%';","note":"Для ролей (MySQL 8) сначала выполните SET ROLE ALL или используйте 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":"Для разрешений, выданных через роли, проверьте также членство в sys.database_role_members.","danger":"safe"},"mongodb":{"command":"db.getUser(\"app\", { showPrivileges: true, showCredentials: false })","note":"showPrivileges выводит также развёрнутые (эффективные) привилегии ролей.","danger":"safe"},"redis":{"command":"ACL GETUSER app","note":"Возвращает разрешённые команды, шаблоны ключей (~), каналы pub/sub (&) и хэши паролей.","danger":"safe"}}},{"id":"change-password","title":"Сменить пароль пользователя","aliases":["change password","reset password","change password","reset password","alter password"],"engines":{"oracle":{"command":"ALTER USER app IDENTIFIED BY \"New#Pass2\";","note":"Чтобы разблокировать учётную запись: ALTER USER app ACCOUNT UNLOCK; Если срок пароля истёк, та же команда его сбрасывает.","danger":"caution"},"postgres":{"command":"ALTER ROLE app WITH PASSWORD 'New#Pass2';","note":"Чтобы пароль не попал в журналы, метакоманда \\password app (psql) хэширует пароль на стороне клиента.","danger":"caution"},"mysql":{"command":"ALTER USER 'app'@'%' IDENTIFIED BY 'New#Pass2';","note":"Убедитесь, что выбрали правильный хост; 'app'@'localhost' — отдельная учётная запись. ALTER USER действует сразу; FLUSH PRIVILEGES не нужен.","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 может требовать смены при первом входе: WITH PASSWORD='...' MUST_CHANGE, CHECK_POLICY=ON.","danger":"caution"},"mongodb":{"command":"db.changeUserPassword(\"app\", \"New#Pass2\")","note":"Убедитесь, что переключились на auth-базу пользователя (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 удаляет все существующие пароли; > только добавляет. Для сохранения выполните ACL SAVE, если используется aclfile, или CONFIG REWRITE, если пользователи заданы в redis.conf (без aclfile ACL SAVE возвращает ошибку).","danger":"caution"}}}]},"rosetta/maintenance.json":{"category":"Обслуживание и администрирование","categoryId":"maintenance","order":6,"concepts":[{"id":"update-statistics","title":"Обновить статистику (для оптимизатора)","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":"Задание AUTO обычно собирает её ночью; при проблеме соберите вручную. Рекомендуется ESTIMATE_PERCENT=>AUTO_SAMPLE_SIZE.","danger":"safe"},"postgres":{"command":"ANALYZE customer;\n-- Both cleanup and analyze: VACUUM ANALYZE customer;","note":"Autovacuum делает это автоматически; ручной ANALYZE после резкой загрузки данных даёт точные планы.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer;","note":"В InnoDB берёт кратковременную блокировку чтения; на больших таблицах запускайте в период низкой активности.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Whole database: EXEC sp_updatestats;","note":"FULLSCAN — самый точный, но и самый дорогой; на больших таблицах может быть предпочтительна выборка (SAMPLE).","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":"Вместо оценки стоимости MongoDB проводит реальные пробы (plan racing); ручная статистика не нужна.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no query optimizer / statistics.","note":"Сложность команд фиксирована; производительность зависит от выбора структуры данных.","danger":"safe"}}},{"id":"rebuild-index","title":"Перестроить / реорганизовать индекс","aliases":["rebuild index","reindex","refresh index","reorganize","index maintenance","defrag index"],"engines":{"oracle":{"command":"ALTER INDEX customer_city_idx REBUILD ONLINE;","note":"ONLINE перестраивает без блокировки таблицы (Enterprise Edition). Для переноса в другое табличное пространство: ... 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+) перестраивает, не блокируя запись в таблицу; медленнее, но безопасно.","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 допускает параллельную запись; INPLACE возможен не для каждого 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":"Типовой порог: фрагментация 5-30% — REORGANIZE, >30% — REBUILD. ONLINE — функция Enterprise.","danger":"caution"},"mongodb":{"command":"db.customer.reIndex()","note":"Перестраивает все индексы в foreground и блокирует коллекцию. В 6.0+ работает только на standalone (на членах набора реплик запрещено); в наборе реплик перестраивайте отдельный индекс через dropIndex + createIndex, узел за узлом (rolling).","danger":"danger"},"redis":{"command":"# Not applicable (core). If RediSearch is used:\n# FT.ALTER / rebuilding is done per the module documentation.","note":"В модели ключ-значение нет традиционного обслуживания индексов.","danger":"safe"}}},{"id":"reclaim-space","title":"Вернуть свободное место (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 понижает high water mark (HWM). Чтобы сжать и индексы, используйте 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 берёт AccessExclusiveLock и перезаписывает таблицу; не запускайте в часы пик. Альтернатива: pg_repack.","danger":"caution"},"mysql":{"command":"OPTIMIZE TABLE customer;","note":"В InnoDB перестраивает таблицу и возвращает пространство data_free; на больших таблицах занимает много времени.","danger":"caution"},"mssql":{"command":"-- Generally NOT RECOMMENDED (causes fragmentation). If really needed:\nDBCC SHRINKFILE (N'MyDB_data', 1024);  -- target 1024 MB","note":"Сжатие файла данных вызывает фрагментацию индексов; после него выполните REBUILD индексов. Для журнала сначала сделайте бэкап.","danger":"danger"},"mongodb":{"command":"db.runCommand({ compact: \"customer\" })","note":"В WiredTiger пытается вернуть свободное место операционной системе (не всегда возможно). В 4.4+ не блокирует чтение/запись, только некоторые DDL-операции над той же коллекцией; так как создаёт большую нагрузку IO, безопаснее запускать на secondary и в окно обслуживания.","danger":"danger"},"redis":{"command":"MEMORY PURGE\n# Tries to return jemalloc fragmentation to the OS","note":"Настройка activedefrag yes выполняет автоматическую онлайн-дефрагментацию; MEMORY DOCTOR даёт рекомендации.","danger":"caution"}}},{"id":"version-uptime","title":"Показать версию и время работы","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 также даёт сведения об экземпляре, например status и 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 удобен для числового сравнения (напр. 160002 = 16.2).","danger":"safe"},"mysql":{"command":"SELECT VERSION();\nSHOW GLOBAL STATUS LIKE 'Uptime';  -- seconds","note":"Uptime — в секундах; разделите на 86400, чтобы получить дни.","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') и ('Edition') дают сведения о версии/редакции.","danger":"safe"},"mongodb":{"command":"db.version()\ndb.serverStatus().uptime   // seconds","note":"serverStatus() также содержит информацию host, process и connections.","danger":"safe"},"redis":{"command":"INFO server\n# redis_version and uptime_in_seconds / uptime_in_days lines","note":"INFO server также даёт информацию os, run_id и tcp_port.","danger":"safe"}}},{"id":"show-config-param","title":"Посмотреть параметр конфигурации","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":"Изменение: ALTER SYSTEM SET ... SCOPE=BOTH; (некоторые только с SCOPE=SPFILE + перезапуск).","danger":"safe"},"postgres":{"command":"SHOW shared_buffers;\n-- Details: SELECT name, setting, unit, context FROM pg_settings WHERE name = 'shared_buffers';","note":"Столбец context показывает, требует ли изменение перезапуска или перечитывания (SELECT pg_reload_conf()).","danger":"safe"},"mysql":{"command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","note":"Учитывайте различие GLOBAL и SESSION: SHOW GLOBAL VARIABLES показывает значение для всего сервера.","danger":"safe"},"mssql":{"command":"SELECT name, value, value_in_use, is_dynamic\nFROM sys.configurations\nWHERE name LIKE '%max server memory%';","note":"Изменение: sp_configure + RECONFIGURE; параметры с is_dynamic=0 требуют перезапуска.","danger":"safe"},"mongodb":{"command":"db.adminCommand({ getParameter: 1, cursorTimeoutMillis: 1 })\n// All startup options: db.adminCommand({ getCmdLineOpts: 1 })","note":"Для изменяемых во время работы используйте setParameter; постоянные настройки хранятся в файле mongod.conf.","danger":"safe"},"redis":{"command":"CONFIG GET maxmemory\n# Wildcard: CONFIG GET max*","note":"CONFIG SET — изменение во время работы; CONFIG REWRITE — для сохранения (записывает в redis.conf).","danger":"safe"}}}]},"rosetta/schema.json":{"category":"Схема и объекты","categoryId":"schema","order":7,"concepts":[{"id":"list-tables","title":"Список таблиц / коллекций","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 возвращает таблицы текущей схемы, all_tables — доступные.","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":"Только для схемы public: WHERE table_schema = 'public'.","danger":"safe"},"mysql":{"command":"SHOW TABLES;\n-- Another schema: SHOW TABLES FROM mydb;","note":"Подробнее: 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":"Короткий вариант: SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';","danger":"safe"},"mongodb":{"command":"show collections\n// or: db.getCollectionNames()","note":"Чтобы видеть и системные коллекции: db.getCollectionInfos().","danger":"safe"},"redis":{"command":"SCAN 0 MATCH * COUNT 100\n# Continue with SCAN <cursor> ... using the returned cursor","note":"В Redis нет «таблиц»; выводятся ключи. НИКОГДА не используйте KEYS * в продакшене (блокирует); SCAN работает через курсор.","danger":"caution"}}},{"id":"describe-table","title":"Показать столбцы / структуру таблицы (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":"Имена таблиц хранятся в верхнем регистре (если созданы без кавычек).","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 показывает также индексы, ограничения и внешние ключи.","danger":"safe"},"mysql":{"command":"DESCRIBE customer;\n-- or: SHOW COLUMNS FROM customer;","note":"Для полного определения используйте SHOW CREATE TABLE customer; (см. понятие «Сгенерировать DDL» ниже).","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 помимо столбцов выводит индексы и ограничения.","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 нет фиксированной схемы; поля могут различаться от документа к документу. Если задан валидатор $jsonSchema, его можно увидеть через db.getCollectionInfos.","danger":"safe"},"redis":{"command":"TYPE mykey        # string | list | set | hash | zset | stream\nOBJECT ENCODING mykey   # internal encoding","note":"Фиксированной схемы нет; тип и кодировка каждого ключа запрашиваются отдельно. Для полей hash используйте HGETALL.","danger":"safe"}}},{"id":"list-indexes","title":"Список индексов таблицы","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":"Для типа/уникальности индекса выполните соединение с user_indexes.","danger":"safe"},"postgres":{"command":"SELECT indexname, indexdef\nFROM pg_indexes\nWHERE tablename = 'customer';","note":"indexdef даёт полный оператор CREATE INDEX; в psql их также выводит \\d customer.","danger":"safe"},"mysql":{"command":"SHOW INDEX FROM customer;","note":"Столбцы Non_unique, Seq_in_index и Cardinality показывают структуру и селективность индекса.","danger":"safe"},"mssql":{"command":"EXEC sp_helpindex 'dbo.customer';\n-- Details: join sys.indexes + sys.index_columns","note":"sp_helpindex возвращает имя, тип и столбцы индекса; для фильтрованных/включённых столбцов используйте представления sys.","danger":"safe"},"mongodb":{"command":"db.customer.getIndexes()","note":"Возвращает ключи каждого индекса и параметры, такие как уникальность и 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":"В модели ключ-значение нет вторичных индексов; они появляются только с модулями вроде RediSearch/RedisJSON.","danger":"safe"}}},{"id":"create-index","title":"Создать индекс","aliases":["create index","add index","build index","new index","createindex"],"engines":{"oracle":{"command":"CREATE INDEX customer_city_idx ON customer(city) ONLINE;","note":"ONLINE строит его без блокировки таблицы. Для уникального используйте CREATE UNIQUE INDEX; для составного — (a, b).","danger":"caution"},"postgres":{"command":"CREATE INDEX CONCURRENTLY customer_city_idx ON customer(city);","note":"CONCURRENTLY не блокирует запись в таблицу, но не может выполняться внутри блока транзакции и работает медленнее.","danger":"caution"},"mysql":{"command":"CREATE INDEX customer_city_idx ON customer(city);\n-- Lock-free: ... ALGORITHM=INPLACE, LOCK=NONE;","note":"InnoDB добавляет большинство индексов онлайн; на больших таблицах разрешайте запись через LOCK=NONE.","danger":"caution"},"mssql":{"command":"CREATE NONCLUSTERED INDEX IX_customer_city\nON dbo.customer (city)\nWITH (ONLINE = ON);","note":"ONLINE — функция Enterprise. Для покрывающих столбцов используйте 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 — порядок по возрастанию, -1 — по убыванию. На больших коллекциях построение сначала на secondary снижает нагрузку.","danger":"caution"},"redis":{"command":"# Not applicable (core). If RediSearch is present:\n# FT.CREATE idx ON HASH PREFIX 1 customer: SCHEMA city TAG","note":"Вторичные индексы появляются только с модулем RediSearch; в базовом Redis доступ — по ключу.","danger":"safe"}}},{"id":"show-ddl","title":"Сгенерировать DDL объекта (оператор CREATE)","aliases":["show ddl","get ddl","create statement","generate ddl","table definition","show create table","generate script"],"engines":{"oracle":{"command":"SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEE', 'HR') FROM dual;","note":"Используйте 'INDEX' для индексов, 'PACKAGE' для пакетов и т. д. Обрезание длинного вывода предотвращает 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":"В psql \\d customer показывает структуру в удобном виде; для полного DDL используйте pg_dump --schema-only.","danger":"safe"},"mysql":{"command":"SHOW CREATE TABLE customer\\G","note":"Возвращает полный CREATE TABLE (столбцы, индексы, движок, кодировку) одной командой.","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":"Для тела процедуры/представления: 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":"Если задан options.validator ($jsonSchema), он — единственное «определение схемы»; индексы получаются отдельно через getIndexes().","danger":"safe"},"redis":{"command":"# Not applicable: there is no schema/DDL concept.","note":"Структуры данных определяются в коде приложения; сохранение — в файлах RDB/AOF.","danger":"safe"}}}]},"rosetta/replication.json":{"category":"Репликация и HA","categoryId":"replication","order":8,"concepts":[{"id":"replication-status","title":"Показать статус и отставание репликации","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 и transport lag — основные метрики отставания Data Guard; выполняйте на 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 — задержка воспроизведения; sync_state = 'sync' означает синхронную реплику.","danger":"safe"},"mysql":{"command":"SHOW REPLICA STATUS\\G\n-- Before 8.0.22: SHOW SLAVE STATUS\\G","note":"Seconds_Behind_Source показывает отставание; Replica_IO_Running и Replica_SQL_Running должны быть 'Yes'. SHOW SLAVE STATUS — псевдоним в 8.0.22–8.3, удалён в 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":"Для Always On. Большой redo_queue_size означает, что вторичная реплика отстаёт.","danger":"safe"},"mongodb":{"command":"rs.printSecondaryReplicationInfo()\n// Detailed status: rs.status()","note":"Показывает, на сколько секунд каждый secondary отстаёт от primary; сравниваются значения optimeDate.","danger":"safe"},"redis":{"command":"INFO replication\n# Check the role, connected_slaves, master_repl_offset,\n# and slaveN:...,offset=... lines","note":"Разница между master_repl_offset и offset реплики даёт отставание; master_link_status должен быть 'up'.","danger":"safe"}}},{"id":"list-replicas","title":"Список реплик / узлов кластера","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":"Конфигурацию Data Guard можно также посмотреть через DGMGRL> SHOW CONFIGURATION.","danger":"safe"},"postgres":{"command":"SELECT application_name, client_addr, state, sync_state\nFROM pg_stat_replication;","note":"Выводит подключённые реплики только на PRIMARY; каждая строка — standby.","danger":"safe"},"mysql":{"command":"SHOW REPLICAS;\n-- Before 8.0.22: SHOW SLAVE HOSTS;","note":"Выполняйте на сервере-источнике; показывает реплики с заданным report-host. SHOW SLAVE HOSTS — псевдоним в 8.0.22–8.3, удалён в 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 различает PRIMARY/SECONDARY; synchronization_health_desc показывает общее состояние.","danger":"safe"},"mongodb":{"command":"rs.status().members.map(m => ({ name: m.name, state: m.stateStr, health: m.health }))","note":"stateStr возвращает PRIMARY/SECONDARY/ARBITER; health 1 означает «исправен».","danger":"safe"},"redis":{"command":"INFO replication\n# If Sentinel is used:\n# SENTINEL replicas <master-name>","note":"Строки slave0, slave1... дают ip/port/offset каждой реплики.","danger":"safe"}}},{"id":"promote-failover","title":"Повысить реплику / выполнить отработку отказа (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 — плановая смена ролей; FAILOVER выполняется, когда primary недоступен, и несёт риск потери данных.","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":"Перед повышением убедитесь, что старый primary действительно остановлен (риск split-brain). После повышения для возврата старого primary как реплики нового primary нужен pg_rewind; для этого должны быть включены wal_log_hints = on или контрольные суммы данных.","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":"Если RESET REPLICA ALL выполнить до полного применения relay log, полученные, но не применённые транзакции теряются. Реплики обычно работают с включёнными read_only/super_read_only; запись не принимается, пока они не отключены. Если старый источник всё ещё доступен для записи, есть риск split-brain. Синтаксис — 8.0.22+ (до этого STOP SLAVE / RESET SLAVE ALL; старая форма удалена в 8.4). Для автоматической и безопасной отработки отказа в продакшене предпочтительны InnoDB Cluster/Group Replication, Orchestrator или 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":"Плановая отработка отказа на синхронную реплику проходит без потери данных; FORCE используется только при аварии.","danger":"danger"},"mongodb":{"command":"// Trigger an election on the current primary:\nrs.stepDown(60)   // step down from primary for 60 s","note":"stepDown запускает новые выборы; чтобы предпочесть конкретный узел, настройте значения priority членов.","danger":"danger"},"redis":{"command":"# Make the replica an independent primary:\nREPLICAOF NO ONE\n# With Sentinel (recommended): SENTINEL FAILOVER <master-name>","note":"Если используется Sentinel/Cluster, вместо ручного REPLICAOF используйте SENTINEL FAILOVER; иначе есть риск split-brain.","danger":"danger"}}},{"id":"open-transactions","title":"Найти открытые / долгие транзакции","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":"Высокий used_ublk означает, что большая транзакция потребляет много undo; она может ожидать 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":"Долгие транзакции в состоянии 'idle in transaction' блокируют VACUUM и очистку кортежей; отслеживайте их.","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":"Используйте trx_mysql_thread_id для сопоставления с PROCESSLIST и при необходимости выполните KILL.","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":"Долгие открытые транзакции мешают усечению журнала и раздувают файл журнала.","danger":"safe"},"mongodb":{"command":"db.adminCommand({\n  currentOp: 1,\n  \"transaction\": { $exists: true }\n})","note":"Долгие многодокументные транзакции автоматически прерываются после transactionLifetimeLimitSeconds (по умолчанию 60 s).","danger":"safe"},"redis":{"command":"# Not applicable: MULTI/EXEC transactions are short and atomic; they do not stay open.","note":"Транзакции Redis не держат блокировок; для долгих одиночных команд проверяйте SLOWLOG.","danger":"safe"}}}]},"rosetta/connect.json":{"category":"Подключение и CLI","categoryId":"connect","order":9,"concepts":[{"id":"connect-cli","title":"Подключиться из командной строки (CLI / строка подключения)","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 — формат «Easy Connect»; как вариант, используйте TNS-алиас из 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":"Чтобы не вводить пароль в командной строке, используйте файл ~/.pgpass или переменную окружения PGPASSWORD.","danger":"safe"},"mysql":{"command":"mysql -h host -P 3306 -u root -p mydb\n# Require TLS: --ssl-mode=REQUIRED","note":"Укажите -p отдельно (пароль будет запрошен); форма 'mysql -pPassword' раскрывает пароль в списке процессов.","danger":"safe"},"mssql":{"command":"sqlcmd -S host,1433 -U sa -P password -d mydb\n# Windows authentication: sqlcmd -S host -E -d mydb","note":"В новом go-sqlcmd для TLS могут понадобиться флаги -C (доверять сертификату сервера) и -N (шифрование).","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 — обычно база, где определён пользователь (чаще всего 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 передаёт пароль в командной строке (выводит предупреждение); для безопасного входа используйте переменную окружения REDISCLI_AUTH.","danger":"safe"}}},{"id":"list-databases","title":"Список баз данных / схем","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":"В Oracle «база данных» соответствует экземпляру; разделение — на уровне схемы (пользователя) или PDB внутри CDB.","danger":"safe"},"postgres":{"command":"SELECT datname FROM pg_database WHERE datistemplate = false;\n-- psql shortcut: \\l","note":"Для схем: SELECT schema_name FROM information_schema.schemata;","danger":"safe"},"mysql":{"command":"SHOW DATABASES;","note":"В MySQL «database» и «schema» — синонимы.","danger":"safe"},"mssql":{"command":"SELECT name, state_desc FROM sys.databases ORDER BY name;","note":"state_desc показывает состояние базы, например ONLINE/OFFLINE/RESTORING.","danger":"safe"},"mongodb":{"command":"show dbs\n// or: db.adminCommand({ listDatabases: 1 })","note":"Пустые базы (в которые ещё не записаны данные) в списке не отображаются.","danger":"safe"},"redis":{"command":"INFO keyspace\n# db0:keys=..., db1:keys=... lines show the logical DBs in use","note":"В Redis есть пронумерованные логические БД (по умолчанию 0-15); в режиме Cluster используется только DB 0.","danger":"safe"}}},{"id":"switch-database","title":"Сменить активную базу данных / схему","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 меняет только разрешение имён объектов, а не привилегии.","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":"В PostgreSQL подключение привязано к одной базе; смена БД означает переподключение.","danger":"safe"},"mysql":{"command":"USE mydb;","note":"Меняет активную схему без разрыва подключения.","danger":"safe"},"mssql":{"command":"USE mydb;","note":"Меняет контекст пакета; последующие команды выполняются в этой базе.","danger":"safe"},"mongodb":{"command":"use mydb","note":"Переключает даже на несуществующую базу; фактически она создаётся при записи первого документа.","danger":"safe"},"redis":{"command":"SELECT 1\n# Switches to a logical DB index between 0 and 15","note":"В режиме Cluster SELECT поддерживает только 0; вместо разделения на логические БД используйте префиксы ключей.","danger":"safe"}}}]},"rosetta/objects.json":{"category":"Управление объектами","categoryId":"objects","order":10,"concepts":[{"id":"create-partition","title":"Создать / разделить / удалить секцию","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":"Поддерживается секционирование RANGE/LIST/HASH. DROP PARTITION удаляет все строки этой секции; с UPDATE INDEXES глобальные индексы остаются валидными. Интервальное секционирование может добавлять секции автоматически.","danger":"caution","priv":"ALTER на таблицу","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":"В базовом PostgreSQL нет SPLIT/MERGE PARTITION (на PG17); для разделения выполните DETACH секции, создайте две секции с новыми границами, перенесите данные и выполните ATTACH. DETACH ... CONCURRENTLY (PG14+) сокращает время блокировки.","danger":"caution","priv":"владелец таблицы","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":"Столбец секционирования должен входить в каждый PRIMARY KEY / UNIQUE-ключ. Для RANGE разделение/слияние выполняется через REORGANIZE PARTITION. DROP PARTITION также удаляет строки (RANGE/LIST).","danger":"caution","priv":"ALTER, DROP на таблицу","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":"Определяются функция и схема секционирования, и таблица создаётся на этой схеме через CREATE TABLE ... ON ps_date(created_at). Перед SPLIT должна быть задана файловая группа NEXT USED. Для удаления данных переключите их в промежуточную таблицу через SWITCH PARTITION.","danger":"caution","priv":"ALTER на таблицу, ALTER ANY DATASPACE","minver":"2016 SP1 (все редакции)"},"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":"Секционирования в классическом смысле нет. Горизонтальное разделение выполняется через чанки при шардинге; для данных по времени time-series-коллекции обеспечивают автоматическую группировку (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 распределяет ключи по 16384 хэш-слотам; это разделение на уровне приложения, а не секционирование таблиц. Ручной команды секционирования нет.","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":"В Elasticsearch разделение достигается делением индекса на шарды, а для временных рядов — созданием новых backing-индексов с ILM + data streams (rollover). Старые индексы можно удалять через ILM.","danger":"safe"}}},{"id":"sequence","title":"Создать последовательность / автоинкрементное значение","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":"Классический способ — CREATE SEQUENCE + NEXTVAL/CURRVAL. С 12c поддерживаются также столбцы GENERATED AS IDENTITY. Значение CACHE повышает производительность, но возможны пропуски.","danger":"safe","priv":"CREATE SEQUENCE","minver":"12c для 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 соответствует стандарту SQL и рекомендуется; serial — устаревшая сокращённая форма. Ручной доступ — через nextval/currval. Значение можно сбросить через setval.","danger":"safe","priv":"CREATE на схему","minver":"PG10 для 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 нет отдельного (стандартного) объекта последовательности; используется столбец AUTO_INCREMENT (один на таблицу, должен быть проиндексирован). MariaDB 10.3+ поддерживает CREATE SEQUENCE.","danger":"safe","priv":"CREATE, ALTER на таблицу"},"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) привязан к столбцу таблицы. CREATE SEQUENCE (2012+) даёт независимый от таблиц счётчик, который можно использовать в нескольких таблицах; значения получают через NEXT VALUE FOR.","danger":"safe","priv":"CREATE SEQUENCE / ALTER на таблицу","minver":"2012 для 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":"По умолчанию _id — это ObjectId (уже уникален и примерно возрастает). Если нужно последовательное целое, генерируйте его атомарно через $inc в отдельной коллекции counters.","danger":"safe"},"redis":{"command":"# Atomic counter\nINCR customer:id\n# or with a specific step\nINCRBY customer:id 10","note":"INCR/INCRBY атомарны и идеально подходят для генерации уникальных возрастающих ID в распределённых средах. Если ключа нет, отсчёт начинается с 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":"Если выполнить POST без ID, ES назначает случайный уникальный _id (не возрастающий). _seq_no — внутренний порядковый номер, растущий с каждой записью, но он предназначен для оптимистичной конкурентности, а не как счётчик приложения.","danger":"safe"}}},{"id":"add-constraint","title":"Добавить или отключить ограничение (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 можно временно отключить через DISABLE/ENABLE (полезно при массовой загрузке). ENABLE VALIDATE проверяет существующие строки; NOVALIDATE — нет.","danger":"caution","priv":"ALTER на таблицу, REFERENCES на целевую"},"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 нет DISABLE для ограничений. Чтобы уменьшить блокировки на большой таблице, добавьте ограничение с NOT VALID и выполните VALIDATE позже. ALTER TABLE ... DISABLE TRIGGER ALL отключает и внутренние триггеры FK и требует superuser; данные, вставленные во время отключения, никогда не проверяются, а повторное включение не проверяет задним числом (риск скрытой потери целостности). Вместо этого выполните DROP ограничения, добавьте его заново с NOT VALID, затем выполните VALIDATE CONSTRAINT.","danger":"caution","priv":"владелец таблицы, REFERENCES на целевую"},"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":"Требуется InnoDB. Вместо отключения отдельного ограничения используется SET FOREIGN_KEY_CHECKS=0 на уровне сессии. Ограничения CHECK реально применяются начиная с MySQL 8.0.16 (раньше игнорировались).","danger":"caution","priv":"ALTER, REFERENCES на таблицу","minver":"8.0.16 для 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":"В SQL Server ограничения FK/CHECK отключаются через NOCHECK CONSTRAINT. Если при повторном включении не использовать WITH CHECK CHECK CONSTRAINT, ограничение остаётся «not trusted», и оптимизатор запросов не может его использовать.","danger":"caution","priv":"ALTER на таблицу, REFERENCES на целевую"},"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":"Внешних ключей нет (обеспечиваются на уровне приложения). Эквивалент UNIQUE — уникальный индекс. Вместо CHECK используйте валидацию $jsonSchema; validationAction: 'warn' даёт эффект отключения.","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":"Понятия реляционной целостности или ограничений нет. Уникальность обеспечивается на стороне приложения через SET ... NX (запись, только если ключа нет) или проверкой отдельного 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":"Связей и ограничений в движке нет. Проверка типов выполняется через mapping. Для эффекта UNIQUE используйте натуральный ключ как _id (записи с тем же _id перезаписываются) или отклоняйте конфликты через op_type=create.","danger":"safe"}}},{"id":"create-view","title":"Создать / изменить представление","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 и создаёт, и изменяет. Для представлений, обновляемых при определённых условиях, можно добавить WITH CHECK OPTION.","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":"С CREATE OR REPLACE новые столбцы можно добавлять только в конец; изменение типа/порядка столбца требует сначала DROP. Простые представления автоматически обновляемы.","danger":"safe","readonly":true,"priv":"CREATE на схему"},"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":"Определение обновляется через CREATE OR REPLACE VIEW или ALTER VIEW. Способ выполнения задаётся через ALGORITHM = MERGE/TEMPTABLE; WITH CHECK OPTION защищает обновляемые представления.","danger":"safe","readonly":true,"priv":"CREATE VIEW на таблицу"},"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+) создаёт/изменяет одним оператором. В старых версиях сначала используйте CREATE, затем ALTER VIEW. Оператор представления должен быть первым в пакете.","danger":"safe","readonly":true,"priv":"CREATE VIEW","minver":"2016 SP1 для 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 создаёт представление только для чтения над исходной коллекцией и конвейером агрегации. В представления нельзя писать; чтобы изменить определение, удалите и создайте заново (или используйте 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":"Представления, определённые через запросы, не поддерживаются. Для похожей задачи храните предвычисленные ключи или постройте поисковый индекс через 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":"Классического SQL-представления нет; ближайший эквивалент — фильтрованный алиас, который автоматически применяет фильтр к запросам. Для выбора столбцов используйте фильтрацию _source в запросе.","danger":"safe","readonly":true}}},{"id":"materialized-view","title":"Создать и обновить материализованное представление","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 требует журнала материализованного представления; COMPLETE ('C') пересчитывает полностью, FAST ('F') — инкрементально. ON COMMIT/ON DEMAND определяет момент обновления.","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 пересчитывает всё (не инкрементально). CONCURRENTLY не блокирует чтение, но требует уникального индекса и работает медленнее.","danger":"caution","priv":"владелец таблицы","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":"Встроенной поддержки материализованных представлений нет. Поддерживается сводная таблица, которая заполняется либо мгновенно триггерами, либо периодически через EVENT SCHEDULER.","danger":"caution","priv":"CREATE, EVENT на схему"},"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 использует индексированные представления: WITH SCHEMABINDING обязателен; после создания уникального кластерного индекса данные физически хранятся и поддерживаются автоматически (отдельный REFRESH не нужен). С GROUP BY обязателен COUNT_BIG(*); если столбец внутри SUM допускает NULL, пишите SUM(ISNULL(amount, 0)) (иначе ошибка 8662). Представление должно создаваться с QUOTED_IDENTIFIER и ANSI_NULLS ON. Автоматическое сопоставление представлений оптимизатором запросов доступно только в редакции Enterprise (и Developer); в Standard обращайтесь к представлению с хинтом WITH (NOEXPAND).","danger":"caution","priv":"CREATE VIEW, ALTER на схему"},"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":"Материализованное представление по требованию получают, записывая результат агрегации в постоянную коллекцию через $out или $merge. Для обновления перезапустите конвейер (вручную или планировщиком).","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":"Понятия материализованного представления нет; ближайший подход — хранить предвычисленные значения в ключах (String/Hash/Sorted Set) и кэшировать их на ограниченное время через 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":"Transform поворачивает (pivot) исходный индекс и материализует его в сводный целевой индекс. Этот пример — пакетный transform: он выполняется один раз через _start и останавливается. Для непрерывного transform, который автоматически обновляется при поступлении новых данных, добавьте в конфигурацию \"sync\": { \"time\": { \"field\": \"<time_field>\", \"delay\": \"60s\" } } с полем времени (настройки continuous: true не существует).","danger":"caution"}}},{"id":"rename-object","title":"Переименовать таблицу / объект","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 или ALTER ... RENAME TO. Зависимые объекты (представления, синонимы) могут стать невалидными и потребовать перекомпиляции.","danger":"caution","priv":"ALTER на объект"},"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 работает для большинства типов объектов. Ссылки внешних ключей и индексов сохраняются автоматически даже при смене имени (на основе OID).","danger":"caution","priv":"владелец таблицы"},"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 атомарен и может переместить несколько таблиц одним оператором. Для переименования столбцов в 8.0+ используйте RENAME COLUMN; до этого нужно использовать CHANGE с указанием типа.","danger":"caution","priv":"ALTER, DROP на старую; CREATE, INSERT на новую"},"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 нет оператора RENAME TABLE; используется системная процедура sp_rename. Префикс схемы указывается в имени таблицы, но не в новом имени. Зависимые представления/процедуры нужно обновить вручную.","danger":"caution","priv":"ALTER на объект, 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 меняет имя коллекции. Чтобы переименовать поле, используйте оператор $rename с updateMany. Шардированные коллекции можно переименовывать в 5.0+; старые версии это не поддерживают.","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 переименовывает ключ; если целевой уже существует, он перезаписывается. RENAMENX работает, только если целевого нет. Если исходного ключа нет, возвращается ошибка.","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":"Команды переименования индекса нет. Переиндексируйте в новое имя через _reindex и удалите старый. Рекомендуется работать через стабильный алиас, чтобы приложение не зависело от имён индексов.","danger":"caution"}}}]},"rosetta/datamove.json":{"category":"Перемещение данных","categoryId":"datamove","order":11,"concepts":[{"id":"bulk-import","title":"Массовая загрузка данных (CSV / из файла)","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":"Самый распространённый инструмент — SQL*Loader (с управляющим файлом). Чтобы читать файл как запрос, используйте внешнюю таблицу. Для больших объёмов быстра прямая загрузка (DIRECT=TRUE).","danger":"caution","priv":"INSERT на таблицу, объект DIRECTORY"},"postgres":{"command":"-- Server side (file on the server; requires superuser/pg_read_server_files)\nCOPY customer (id, name, email) FROM '/data/customer.csv' WITH (FORMAT csv, HEADER true);\n\n-- Client side (psql, file on the client)\n\\copy customer (id, name, email) FROM 'customer.csv' WITH (FORMAT csv, HEADER true)","note":"COPY читает файл на сервере и требует привилегий; psql \\copy читает файл клиента и не требует дополнительных привилегий. Это самый быстрый способ загрузить CSV.","danger":"caution","priv":"INSERT на таблицу (pg_read_server_files для 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 — самый быстрый способ массовой загрузки. LOCAL читает файл клиента и должен быть включён и на сервере (local_infile=ON), и на клиенте. secure_file_priv ограничивает путь на сервере.","danger":"caution","priv":"FILE (INSERT для 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 читает файл по пути, доступному учётной записи службы SQL Server. bcp — утилита командной строки; -c — символьный режим, -t — разделитель полей. Для максимальной скорости добивайтесь минимального журналирования с 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 загружает CSV/TSV/JSON; --headerline считает первую строку именами полей. Для обновления существующих документов используйте --mode=upsert --upsertFields. Для больших данных ускоряет --numInsertionWorkers.","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":"Для массовой загрузки используйте redis-cli --pipe (mass insert); он очень быстр, так как передаёт команды в формате протокола RESP через одно подключение. Как вариант, команды группируются через 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":"API _bulk принимает NDJSON (строка действия + строка данных на документ; файл должен заканчиваться \\n). Для CSV используйте Logstash, Filebeat или elasticdump. Отключение refresh_interval на время загрузки ускоряет её.","danger":"caution","priv":"привилегия index / create index"}}},{"id":"bulk-export","title":"Выгрузить данные (CSV / в файл)","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":"Для CSV используйте SQL*Plus SET MARKUP CSV ON + SPOOL (12.2+). Для переноса схем/таблиц Data Pump (expdp) создаёт бинарный .dmp, который импортируется в другую базу Oracle через impdp.","danger":"safe","readonly":true,"priv":"SELECT на таблицу","minver":"12.2 для 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 записывает результат запроса в файл (на сервере, требует привилегий); psql \\copy пишет в файл клиента. HEADER true добавляет строку заголовка.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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 создаёт файл на сервере (требуются путь secure_file_priv и привилегия FILE; перезаписывать не может). Для вывода на стороне клиента перенаправляйте через 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 записывает данные в файл: -c — символьный режим, -t, — разделитель полей, -n — нативный бинарный формат. Для результата запроса используйте queryout. Также можно использовать «Results to File» в SSMS.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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 создаёт CSV/JSON; для CSV столбцы нужно явно указать через --fields. Для фильтрации используйте --query. Для полного бинарного бэкапа предпочтителен mongodump.","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":"Встроенной структурированной выгрузки в CSV нет. Для списка ключей используйте --scan (неблокирующий), для полных данных — файл RDB: redis-cli --rdb скачивает в локальный файл снимок, сгенерированный в фоне на сервере; если файл нужен на сервере, используйте BGSAVE. SAVE блокирует основной поток; не используйте в продакшене. Чтобы получить и значения, читайте результат сканирования в цикле.","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":"Прямой выгрузки в CSV нет. Чтобы получить все данные, используйте elasticdump или постранично читайте _search через scroll/PIT (point-in-time). Для преобразования в CSV нужен вывод csv в Logstash или внешний инструмент.","danger":"safe","readonly":true,"priv":"read на индекс"}}},{"id":"copy-between","title":"Копировать данные между таблицами / средами","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":"В пределах одной базы используйте INSERT ... SELECT или CREATE TABLE AS SELECT (CTAS). Для копирования между средами предпочтительны database link (@remote_db) или Data Pump. Хинт APPEND ускоряет копирование через direct-path.","danger":"caution","priv":"SELECT на источник, INSERT/CREATE на цель"},"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":"В пределах одной БД используйте INSERT ... SELECT / CTAS. Между серверами — конвейер pg_dump | psql или копирование через внешнюю таблицу с postgres_fdw.","danger":"caution","priv":"SELECT на источник, INSERT/CREATE на цель"},"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":"В пределах одной БД — INSERT ... SELECT или CREATE TABLE ... AS SELECT. Между разными серверами обычно передают вывод mysqldump клиенту mysql через конвейер. CTAS не копирует индексы и ограничения.","danger":"caution","priv":"SELECT на источник, INSERT/CREATE на цель"},"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":"В SQL Server копирование в новую таблицу выполняется через SELECT ... INTO (эквивалент CTAS). Между серверами используйте linked server (четырёхсоставное имя), bcp out/in или SSIS.","danger":"caution","priv":"SELECT на источник, INSERT / CREATE TABLE на цель"},"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 полностью заменяет целевую коллекцию; $merge сливает с существующими документами или вставляет в них (в той же или другой БД). Для переноса между серверами используйте mongodump | mongorestore или mongoexport/mongoimport.","danger":"caution","priv":"find на источник, insert на цель"},"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 атомарно переносит ключ на целевой экземпляр и удаляет его из источника (с опцией COPY не удаляет). Для копирования в пределах одного экземпляра используйте команду COPY (6.0+) или 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 копирует между индексами; можно фильтровать запросом и преобразовывать скриптом. Между кластерами используйте remote reindex (на целевом должен быть задан reindex.remote.whitelist). Большие задания распараллеливаются через slices.","danger":"caution","priv":"read на источник, write на цель"}}},{"id":"generate-insert","title":"Сгенерировать данные в виде скрипта INSERT","aliases":["generate insert","insert script","generate script","data dump","inserts","generate script"],"engines":{"oracle":{"command":"-- DBMS_METADATA for object DDL\nSELECT DBMS_METADATA.GET_DDL('TABLE','CUSTOMER') FROM dual;\n\n-- Generating row data as INSERTs is not built in;\n-- use SQL Developer 'Export > Insert' or SQLcl 'SET SQLFORMAT INSERT':\n-- SET SQLFORMAT INSERT\n-- SELECT * FROM customer;","note":"В Oracle нет SQL-команды, напрямую превращающей данные в готовый скрипт INSERT. Для DDL используйте DBMS_METADATA.GET_DDL; для INSERT строк — SQLcl (SET SQLFORMAT INSERT) или мастер Export в SQL Developer.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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 записывает каждую строку отдельным INSERT; --column-inserts добавляет и имена столбцов (устойчиво к различиям схем). По умолчанию (без флага) создаётся формат COPY, который загружается намного быстрее.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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 по умолчанию создаёт операторы INSERT; --no-create-info пропускает CREATE TABLE и выводит только данные. --skip-extended-insert делает каждую строку отдельным INSERT (по умолчанию много строк объединяется в один оператор).","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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 нет встроенной команды T-SQL для этого. Самый распространённый способ — выбрать Data only в мастере «Generate Scripts» в SSMS. В командной строке утилита Microsoft mssql-scripter генерирует INSERT с --data-only.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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":"Понятия SQL INSERT нет. Для переносимого бэкапа используйте mongodump (BSON, восстанавливается через mongorestore). Для insertMany, встраиваемого в код, можно использовать вывод mongoexport --jsonArray.","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":"Скрипт SQL INSERT не создаётся. Используйте сериализацию DUMP + RESTORE для каждого ключа и файл RDB (--rdb) для всех данных. Если нужен список команд, итерируйте через SCAN и стройте соответствующие команды SET/HSET для каждого типа.","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":"SQL INSERT не создаётся; в Elasticsearch единица загрузки — NDJSON для _bulk. Чтобы извлечь существующие данные в пригодном для повторной загрузки виде, используйте elasticdump (type=data); вывод _search + PIT можно преобразовать в NDJSON внешним скриптом.","danger":"safe","readonly":true,"priv":"read на индекс"}}}]},"rosetta/monitoring.json":{"category":"Мониторинг и диагностика","categoryId":"monitoring","order":12,"concepts":[{"id":"current-locks","title":"Список текущих блокировок","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":"Показывает активные блокировки и блокирующую сессию (blocking_session) через v$lock и v$locked_object.","danger":"safe","readonly":true,"priv":"SELECT на 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":"Соединяет pg_locks с pg_stat_activity; строки с granted=false показывают ожидающие (заблокированные) блокировки.","danger":"safe","readonly":true,"priv":"роль pg_monitor (чтобы видеть все запросы)"},"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 выводит текущие блокировки InnoDB, а data_lock_waits — связи ожидания.","danger":"safe","readonly":true,"priv":"SELECT на 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 показывает текущие запросы блокировок; запросы с request_status = WAIT заблокированы.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"$or\": [ { \"waitingForLock\": true }, { \"locks\": { \"$exists\": true } } ] })","note":"db.currentOp показывает операции, ожидающие блокировку (waitingForLock), и операции, удерживающие блокировки; MongoDB блокирует на уровне документа/коллекции, а не строки.","danger":"safe","priv":"роль inprog / clusterMonitor"},"redis":{"command":"# Not applicable: Redis has no row/table lock concept; commands run sequentially on a single thread. For blocking analysis, use the SLOWLOG and LATENCY commands.","note":"Redis однопоточный, поэтому традиционного списка блокировок нет.","danger":"safe"},"elasticsearch":{"command":"GET _cluster/pending_tasks","note":"В Elasticsearch нет блокировок строк; здесь выводятся задачи изменения кластера в очереди на master (pending tasks).","danger":"safe","readonly":true}}},{"id":"long-operations","title":"Прогресс долгих операций","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 показывает процент выполнения и оставшееся время (time_remaining) операций, длящихся более 6 секунд.","danger":"safe","readonly":true,"priv":"SELECT на 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":"Представления pg_stat_progress_* показывают прогресс операций VACUUM, CREATE INDEX, CLUSTER, ANALYZE и COPY.","danger":"safe","readonly":true,"minver":"9.6"},"mysql":{"command":"SELECT t.processlist_id, st.event_name, st.work_completed, st.work_estimated,\n       ROUND(st.work_completed / NULLIF(st.work_estimated,0) * 100, 2) AS pct\nFROM performance_schema.events_stages_current st\nJOIN performance_schema.threads t ON t.thread_id = st.thread_id\nWHERE st.work_estimated > 0;","note":"При включённом инструментировании этапов events_stages_current показывает прогресс через work_completed/work_estimated (напр. ALTER TABLE).","danger":"safe","readonly":true,"priv":"SELECT на 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 даёт процент для операций BACKUP, RESTORE, DBCC и перестроения индексов.","danger":"safe","readonly":true,"priv":"VIEW SERVER STATE"},"mongodb":{"command":"db.currentOp({ \"active\": true, \"secs_running\": { \"$gte\": 3 } })","note":"В выводе db.currentOp прогресс показывают долгие операции, а для построения индексов — поле 'progress' (done/total).","danger":"safe","priv":"роль inprog / clusterMonitor"},"redis":{"command":"INFO persistence\n# Keys: rdb_bgsave_in_progress, aof_rewrite_in_progress, rdb_last_bgsave_status, loading, loading_loaded_perc","note":"Раздел INFO persistence сообщает, выполняются ли сохранение или перезапись RDB/AOF, и процент загрузки.","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 показывает статус долгих задач (reindex, update_by_query); _cat/recovery — прогресс активного восстановления шардов.","danger":"safe","readonly":true}}},{"id":"session-trace","title":"Трассировка сессии / запроса (trace / профилирование)","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 создаёт файл трассировки 10046 для сессии; по завершении всегда отключайте её (трассировка добавляет накладные расходы и занимает диск).","danger":"caution","priv":"EXECUTE на 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 записывает в журнал планы запросов, а log_min_duration_statement — медленные/все запросы; значение 0 журналирует каждый запрос, следите за объёмом журнала.","danger":"caution","priv":"SUPERUSER (для LOAD) или 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":"Трассировка запросов выполняется через events_statements в performance_schema; метод SET profiling старый (устарел), но даёт быстрое измерение для сессии.","danger":"caution","priv":"SELECT/UPDATE на 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) — современный метод трассировки; создайте сессию и запустите её через START, а по завершении остановите через STOP/DROP.","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) записывает все операции в коллекцию system.profile; в продакшене предпочтителен уровень 1 (только медленные), так как уровень 2 добавляет накладные расходы.","danger":"caution","priv":"dbAdmin / enableProfiler"},"redis":{"command":"MONITOR\n# Streams all commands arriving at the server in real time. Exit: Ctrl-C","note":"MONITOR показывает все команды в реальном времени, но серьёзно снижает производительность; в продакшене используйте кратко и осторожно. Альтернатива: 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 даёт разбивку времени запроса/агрегации для одного поиска; для постоянного мониторинга настройте пороги slowlog.","danger":"safe","readonly":true}}},{"id":"top-wait-events","title":"Основные события ожидания","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 даёт накопленные события ожидания; v$active_session_history — выборку активных сессий за последние несколько минут (требуется лицензия Diagnostics Pack).","danger":"safe","readonly":true,"priv":"SELECT на 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 показывает текущее состояние ожидания; регулярный опрос (polling) выявляет самые частые события ожидания.","danger":"safe","readonly":true,"priv":"роль pg_monitor"},"mysql":{"command":"SELECT event_name, count_star, sum_timer_wait,\n       ROUND(sum_timer_wait/1000000000000, 2) AS sec_waited\nFROM performance_schema.events_waits_summary_global_by_event_name\nWHERE count_star > 0\nORDER BY sum_timer_wait DESC\nLIMIT 20;","note":"events_waits_summary_global_by_event_name даёт суммарное время ожидания по событиям (в пикосекундах); инструменты ожиданий должны быть включены.","danger":"safe","readonly":true,"priv":"SELECT на 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 показывает накопленные типы ожиданий с момента запуска экземпляра; сбрасывается через 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 нет классических событий ожидания; на перегрузку указывают исчерпание concurrentTransactions (тикетов), globalLock.currentQueue и давление на кэш.","danger":"safe","priv":"роль clusterMonitor"},"redis":{"command":"CONFIG SET latency-monitor-threshold 100\nLATENCY LATEST\nLATENCY HISTORY command\n# For the slowest commands: SLOWLOG GET 10","note":"После задания порога latency monitor LATENCY LATEST/HISTORY показывает события, вызывающие наибольшую задержку.","danger":"safe"},"elasticsearch":{"command":"GET _nodes/hot_threads","note":"_nodes/hot_threads выявляет узкие места, показывая стеки потоков, потребляющих больше всего CPU.","danger":"safe","readonly":true}}},{"id":"os-resource","title":"Использование ресурсов сервера изнутри БД (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 даёт счётчики CPU/памяти ОС; v$sysmetric — метрики БД в реальном времени (CPU, IOPS) за последний интервал.","danger":"safe","readonly":true,"priv":"SELECT на 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 даёт счётчики чтения/записи/попаданий в кэш; PostgreSQL не показывает использование CPU, поэтому нужны средства операционной системы.","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":"Сводные представления схемы sys (host_summary) дают разбивку IO/задержек; SHOW ENGINE INNODB STATUS показывает активность buffer pool и IO.","danger":"safe","readonly":true,"priv":"SELECT на 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 даёт счётчики производительности БД; dm_os_ring_buffers — соотношение CPU процесса SQL/других процессов за последние ~256 минут.","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() даёт метрики памяти, сети, opcounters и кэша WiredTiger; для детальных CPU/IO ОС используйте mongostat или средства операционной системы.","danger":"safe","priv":"роль clusterMonitor"},"redis":{"command":"INFO cpu\nINFO stats\n# used_cpu_sys, used_cpu_user, instantaneous_ops_per_sec, total_net_input_bytes","note":"INFO cpu показывает потребление CPU процессом Redis, а INFO stats — операции в секунду и сетевой трафик.","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 даёт процент CPU, память и метрики процесса по каждому узлу; для диска используйте раздел fs.","danger":"safe","readonly":true}}}]},"rosetta/automation.json":{"category":"Автоматизация и аудит","categoryId":"automation","order":13,"concepts":[{"id":"schedule-job","title":"Создать задание по расписанию","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 определяет задание по расписанию; repeat_interval задаёт периодичность календарным выражением.","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":"Расширение pg_cron определяет задания в стандартном синтаксисе cron через cron.schedule; если расширение не установлено, используйте pgAgent или cron операционной системы.","danger":"caution","priv":"установлен pg_cron; обычно superuser/владелец расширения"},"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 работает через event scheduler; глобальная переменная event_scheduler должна быть ON, иначе события не срабатывают.","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 создаются процедурами msdb: определяются задание, шаг и расписание и привязываются к серверу. Служба SQL Server Agent должна быть запущена.","danger":"caution","priv":"SQLAgentUserRole или 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":"В редакции Community нет заданий по расписанию; нужен внешний планировщик или Atlas Trigger.","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 не выполняет задания по расписанию; это решается через TTL/keyspace notifications и внешний 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) делает снимки по расписанию; ILM используется для жизненного цикла данных, а Watcher — для расширенной автоматизации.","danger":"caution","priv":"кластерная привилегия manage_slm"}}},{"id":"list-jobs","title":"Список заданий по расписанию / их статус","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 показывает определённые задания и их состояния; dba_scheduler_job_run_details — успешность/неудачу прошлых запусков.","danger":"safe","readonly":true,"priv":"SELECT на 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":"С pg_cron cron.job выводит определённые задания, а cron.job_run_details даёт статус и сообщение каждого запуска.","danger":"safe","readonly":true,"priv":"установлен pg_cron; доступ к схеме cron"},"mysql":{"command":"SELECT event_schema, event_name, status, interval_value, interval_field, last_executed\nFROM information_schema.events\nORDER BY last_executed DESC;","note":"information_schema.events выводит определённые события, их статус (ENABLED/DISABLED) и время последнего выполнения.","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 даёт определённые задания Agent, а sysjobhistory — историю запусков и результат (run_status).","danger":"safe","readonly":true,"priv":"SQLAgentReaderRole / доступ к msdb"},"mongodb":{"command":"# Not applicable: there is no built-in scheduler, so there are no jobs to list. If Atlas Triggers are used, view them in the Atlas UI/API.","note":"В Community MongoDB нет таблицы заданий; Atlas Triggers управляются в интерфейсе Atlas.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of scheduled jobs, so there are no jobs to list.","note":"В Redis нет планировщика заданий; выводить нечего.","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 выводит политики снимков, _ilm/policy — политики жизненного цикла, а _watcher/stats показывает состояние выполнения Watcher.","danger":"safe","readonly":true,"priv":"привилегия read_slm / read_ilm"}}},{"id":"enable-audit","title":"Включить аудит / журналирование","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":"В Unified Auditing политика создаётся и включается через AUDIT; записи хранятся в представлении unified_audit_trail.","danger":"caution","priv":"роль AUDIT SYSTEM / AUDIT_ADMIN","minver":"12.1"},"postgres":{"command":"-- pgaudit extension (recommended; first shared_preload_libraries = 'pgaudit' + restart + CREATE EXTENSION pgaudit):\nALTER SYSTEM SET pgaudit.log = 'write, ddl';\nSELECT pg_reload_conf();\n-- Simple built-in method:\nALTER SYSTEM SET log_statement = 'ddl';\nSELECT pg_reload_conf();","note":"pgaudit обеспечивает детальный журнал аудита; задавайте его через ALTER SYSTEM, чтобы он действовал на весь сервер (SET на уровне сессии аудирует только эту сессию). Без расширения базовое журналирование включается через log_statement ('ddl','mod','all'). Следите за объёмом журнала.","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":"Плагин audit_log (Enterprise, или server_audit в MariaDB) ведёт детальный журнал аудита; general_log журналирует все запросы, но снижает производительность.","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":"Сначала создайте и включите цель SERVER AUDIT (файл/журнал событий), затем определите отслеживаемые события через серверную или базовую 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":"Аудит доступен только в Enterprise и Atlas; включается разделом auditLog конфигурации mongod. В редакции Community его нет.","danger":"caution"},"redis":{"command":"ACL LOG\n# Shows rejected authentication/command/key access attempts.\n# To reset: ACL LOG RESET\n# For general command auditing: an external proxy or MONITOR (with care).","note":"ACL LOG автоматически фиксирует события безопасности (неудачная аутентификация, неразрешённые команды/ключи); полного аудита команд встроенно нет.","danger":"safe","priv":"разрешение ACL (по умолчанию admin)"},"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":"Журнал аудита включается через xpack.security.audit.enabled; задаётся в elasticsearch.yml и требует перезапуска узла (не динамический). Требуется коммерческая лицензия.","danger":"caution"}}},{"id":"current-config-diff","title":"Список параметров, отличных от значений по умолчанию","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":"Строки v$parameter с isdefault = 'FALSE' показывают параметры, отличающиеся от значений по умолчанию (заданные явно).","danger":"safe","readonly":true,"priv":"SELECT на v$parameter"},"postgres":{"command":"SELECT name, setting, unit, source\nFROM pg_settings\nWHERE source NOT IN ('default', 'override')\nORDER BY name;","note":"Если pg_settings.source отличается от 'default' и 'override', параметр изменён файлом конфигурации, командной строкой или сессией.","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 возвращает все параметры; отличие от значений по умолчанию напрямую не показывает. Столбец VARIABLE_SOURCE в performance_schema.variables_info показывает изменения, отличные от скомпилированного значения по умолчанию (файл, командная строка, динамически).","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 возвращает параметры сервера; если value и value_in_use различаются, есть изменение, не применённое через RECONFIGURE (или ожидающее перезапуска). sys.configurations не хранит значение по умолчанию (minimum/maximum — лишь допустимый диапазон); чтобы увидеть отличия, сравнивайте значения с документированными значениями по умолчанию.","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":"Раздел 'parsed' команды getCmdLineOpts выводит только явно заданные (не по умолчанию) параметры.","danger":"safe","priv":"привилегия clusterMonitor / hostInfo"},"redis":{"command":"CONFIG GET *\n# Redis lists all settings but does not compare them with defaults.\n# To see differences, compare the output with the default redis.conf values for your version.","note":"CONFIG GET * возвращает все текущие параметры; встроенного сравнения со значениями по умолчанию нет, поэтому сравнивайте вывод со стандартными значениями redis.conf соответствующей версии.","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 возвращает параметры кластера, отличные от значений по умолчанию (persistent/transient); с include_defaults=true для сравнения показываются и значения по умолчанию.","danger":"safe","readonly":true}}}]},"rosetta/lifecycle.json":{"category":"Установка и обновление","categoryId":"lifecycle","order":14,"concepts":[{"id":"check-version","title":"Показать установленную версию и уровень патчей","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":"Баннер v$version даёт версию. dba_registry_sqlpatch показывает, зарегистрированы ли применённые Release Updates (RU) и one-off патчи на уровне SQL, а 'opatch lspatches' — бинарные патчи в home.","danger":"safe","readonly":true,"priv":"SELECT на v$version, dba_registry_sqlpatch"},"postgres":{"command":"SELECT version();\n-- Numeric version only:\nSHOW server_version;\nSELECT current_setting('server_version_num');","note":"version() возвращает полный текст, включая сведения о сборке. В PostgreSQL нет отдельного номера патча; минорная версия (напр. 16.3) отражает уровень исправлений.","danger":"safe","readonly":true},"mysql":{"command":"SELECT VERSION();\n-- Detailed build information:\nSHOW VARIABLES LIKE 'version%';","note":"VERSION() возвращает, напр., '8.0.36'; последний компонент — уровень патча (релиза). Переменная version_comment показывает, сборка Community/Enterprise или 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 даёт применённый Cumulative Update (напр. CU12), а ProductUpdateReference — связанный номер KB. ProductLevel показывает 'RTM' или, в старых версиях, 'SP'.","danger":"safe","readonly":true},"mongodb":{"command":"db.version()\n// Detailed build information:\ndb.serverBuildInfo()\n// or: db.adminCommand({ buildInfo: 1 })","note":"db.version() возвращает строку версии (напр. '7.0.11'). В MongoDB исправления выходят как новая патч-версия; отдельного понятия «уровень патчей» нет.","danger":"safe"},"redis":{"command":"INFO server\n# redis-cli one-liner: redis-cli INFO server | grep redis_version","note":"Поле redis_version в выводе INFO server даёт установленную версию. В Redis исправления выходят как новая патч-версия (напр. 7.2.5); поле redis_git_sha1 также показывает идентификатор сборки.","danger":"safe"},"elasticsearch":{"command":"GET /\n# curl: curl -s http://localhost:9200/","note":"Корневой endpoint возвращает version.number (напр. '8.14.1'), lucene_version и build_flavor. Версии всех узлов: GET _cat/nodes?v&h=name,version","danger":"safe","readonly":true}}},{"id":"check-upgrade-readiness","title":"Проверка совместимости/готовности перед обновлением","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":"Режим 'analyze' AutoUpgrade создаёт отчёт перед обновлением без изменения данных; в нём перечислены предупреждения, требующие исправления, и пункты, которые будут решены автоматически. Всегда запускайте его перед реальным обновлением.","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 проверяет осуществимость обновления, не меняя данных (сообщает об отсутствующих расширениях, несовместимых типах данных и т. п.). --check можно запускать и при работающем старом кластере; само обновление выполняется при остановленных обоих кластерах.","danger":"safe"},"mysql":{"command":"-- 8.0 and later: Upgrade Checker utility (MySQL Shell):\n-- $ mysqlsh -- util check-for-server-upgrade { --user=root --host=localhost }\n-- Table consistency for legacy 5.7->8.0:\n$ mysqlcheck --all-databases --check-upgrade -u root -p","note":"util.checkForServerUpgrade() в MySQL Shell сообщает об удалённых функциях, конфликтах с зарезервированными словами и несовместимых определениях. Начиная с 8.0 сервер сам выполняет обновление словаря при запуске; отдельный mysql_upgrade больше не нужен.","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) заменил старый Upgrade Advisor; он сообщает об устаревших функциях, несовместимых изменениях и блокерах по каждой базе. Запустите его перед обновлением и устраните найденное.","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":"Перед обновлением бинарной версии FCV должен совпадать с текущей версией; иначе обновление блокируется. В MongoDB нет автоматического «сканера устаревших функций»; примечания о совместимости релизов просматриваются вручную.","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 в целом совместимы вперёд; так как официального сканера готовности нет, проверяйте формат файлов RDB/AOF, удалённые директивы конфигурации и изменения команд вручную по release notes.","danger":"safe"},"elasticsearch":{"command":"GET _migration/deprecations\n# For a specific component: GET <index>/_migration/deprecations","note":"Deprecation Info API перечисляет параметры кластера, узлов, индексов и ML, которые вызовут проблемы при переходе на следующую мажорную версию, как 'critical' / 'warning'. Kibana Upgrade Assistant визуализирует те же данные.","danger":"safe","readonly":true}}},{"id":"apply-patch","title":"Применить патч","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 обновляет бинарные файлы в Oracle Home; затем datapatch применяет изменения словаря/SQL в открытой базе и регистрирует их в dba_registry_sqlpatch. В RAC патчи с поддержкой rolling можно применять узел за узлом.","danger":"caution","priv":"ОС: владелец ORACLE_HOME; SQL: SYSDBA"},"postgres":{"command":"# Minor version patch = replace binaries + restart (no dump needed):\n$ sudo systemctl stop postgresql-16\n$ sudo dnf update postgresql16-server   # or apt upgrade\n$ sudo systemctl start postgresql-16","note":"Минорные патчи в пределах одной мажорной серии (16.3 -> 16.4) не меняют формат данных: достаточно обновить пакет и перезапустить службу; pg_upgrade или dump/restore не нужны.","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+ автоматически обновляет словарь данных при запуске; отдельный шаг mysql_upgrade не нужен. При репликации сначала обновляйте реплики, затем источник.","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) и GDR (General Distribution Release) применяются установщиком setup и требуют перезапуска экземпляра. В группе Always On сначала обновляйте вторичные реплики и выполняйте переключение, чтобы минимизировать простой.","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":"Патч-обновления в пределах одной мажорной версии (7.0.x) выполняются поочерёдно (rolling), сначала secondary. Primary обновляется после передачи роли через rs.stepDown(), что минимизирует простой записи.","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":"Патч-версии (7.2.x) применяются через rolling restart: сначала обновляются и ресинхронизируются реплики, затем реплика повышается до master и обновляется старый master. Sentinel/Cluster автоматизирует отработку отказа.","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":"Патч/минорные обновления выполняются через rolling upgrade: размещение шардов ограничивается, узлы по одному обновляются и перезапускаются, затем размещение снова включается и ожидается возврат кластера в green.","danger":"caution"}}},{"id":"upgrade-major","title":"Обновление мажорной версии","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 одной командой выполняет анализ, предварительные исправления, обновление и пост-шаги; может обрабатывать несколько баз последовательно, сокращая простой. DBUA делает то же через пошаговый интерфейс. Полный бэкап перед этим обязателен.","danger":"danger","priv":"SYSDBA + ОС: владелец нового ORACLE_HOME"},"postgres":{"command":"# pg_upgrade to the new major version (with both clusters stopped):\n$ pg_upgrade \\\n    --old-datadir=/var/lib/pgsql/16/data \\\n    --new-datadir=/var/lib/pgsql/17/data \\\n    --old-bindir=/usr/pgsql-16/bin \\\n    --new-bindir=/usr/pgsql-17/bin \\\n    --link   # fast migration with hard links\n# Then gather statistics (analyze_new_cluster.sh was removed in PG14):\n$ vacuumdb --all --analyze-in-stages","note":"pg_upgrade выполняет переход на мажорную версию (16 -> 17) на месте; режим --link сокращает время, создавая жёсткие ссылки вместо копирования файлов, но делает старый кластер непригодным. После перехода статистику нужно собрать заново.","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":"Начиная с MySQL 8.x поддерживаются мажорные обновления на месте: сервер, запущенный с новыми бинарными файлами, автоматически преобразует словарь данных и системные таблицы (старый отдельный шаг mysql_upgrade удалён). Обновляйтесь по одной мажорной версии за раз.","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 обновляет экземпляр на месте до новой мажорной версии. Более безопасный способ — параллельная установка (side-by-side), затем перенос баз через backup/restore и повышение уровня совместимости отдельным шагом.","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":"Мажорное обновление в MongoDB проходит в два этапа: сначала бинарные файлы всех узлов поочерёдно переводятся на новую версию, затем, когда кластер стабилен, FCV повышается до новой версии. Подниматься можно только на одну мажорную версию за раз; FCV управляется отдельно, чтобы сохранить возможность отката.","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":"Переход Redis на мажорную версию (6.x -> 7.x) выполняется так: на новой версии поднимается реплика, синхронизируется и затем повышается до master; простой записи таким образом сводится почти к нулю. Файлы RDB совместимы с последующими версиями.","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 ни одним способом не поддерживает пропуск мажорных версий: и rolling upgrade, и full-cluster restart переходят только на следующую мажорную (n -> n+1) и требуют, чтобы источник был на последней минорной (напр. 7.17). Индексы, созданные двумя мажорными версиями ранее (напр. в 6.x), в новой версии не открываются; их нужно пересоздать через _reindex до перехода (8.x может читать их лишь ограниченно, как read-only архивные индексы). Перед переходом сделайте снимок и устраните предупреждения об устаревании.","danger":"danger"}}},{"id":"set-compatibility","title":"Задать уровень совместимости / версию функций","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":"Параметр COMPATIBLE определяет, функции и форматы файлов какой версии включены. После повышения и перезапуска экземпляра его НЕЛЬЗЯ ВЕРНУТЬ (откат блокируется); поэтому повышайте его постепенно, пока успешность обновления не подтверждена.","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 нет понятия «уровень совместимости», как в SQL Server/Oracle; поведение версии напрямую связано с бинарной версией. Переход между версиями выполняется через pg_dump/pg_restore или 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 нет уровня совместимости по версиям; поведение SQL в основном зависит от бинарной версии и настройки sql_mode. Тонко настраивайте sql_mode, чтобы сохранить поведение старого приложения.","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 определяет, как какой версии ведут себя оптимизатор запросов и языковые функции (150=2019, 160=2022). После обновления экземпляра базы могут оставаться на старом уровне; рекомендуется повышать его постепенно, отслеживая регрессии планов. Его можно снова понизить.","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 управляет моментом включения изменений формата постоянных данных и новых функций версии. Повышайте его после обновления бинарных файлов при стабильном кластере; понижение возможно, только если та же бинарная версия ещё поддерживает более низкий FCV.","danger":"caution","priv":"роль clusterManager / root"},"redis":{"command":"# Not applicable: Redis has no compatibility level / feature version concept.\n# Behavior depends directly on the running binary version.","note":"В Redis нет настраиваемого уровня совместимости; набор функций полностью зависит от работающей версии. Совместимость сохраняется только на уровне формата файлов RDB/AOF, в прямом направлении.","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 нет настройки уровня совместимости; каждый индекс несёт формат мажорной версии, в которой создан. Индексы предыдущей мажорной версии читаются, а более старые нужно перенести в новую версию через _reindex.","danger":"safe"}}},{"id":"post-upgrade-tasks","title":"Задачи после обновления (перекомпиляция/анализ)","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 перекомпилирует пакеты/представления/триггеры, ставшие INVALID после обновления. Последующий сбор статистики по словарю и фиксированным объектам позволяет оптимизатору строить корректные планы в новой версии.","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 и ранее) не переносит статистику планировщика; analyze-in-stages быстро делает систему работоспособной, собирая сначала грубую, затем полную статистику. Расширения нужно обновить через ALTER EXTENSION ... UPDATE, а индексы — через REINDEX, если изменилась collation.","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":"В MySQL 8.0.16 и новее функция mysql_upgrade перенесена в сервер, поэтому отдельно не запускается. После обновления обновление статистики через ANALYZE TABLE на больших/критичных таблицах сохраняет качество планов.","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":"После обновления DBCC UPDATEUSAGE исправляет неточные счётчики пространства/строк, унаследованные от старых версий, а sp_updatestats обновляет статистику. При повышении уровня совместимости отслеживайте регрессии планов, которые может вызвать новый Cardinality Estimator (CE).","danger":"caution"},"mongodb":{"command":"// 1) Verify the FCV upgrade:\ndb.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })\n// 2) Check indexes and compatibility warnings in the server logs:\ndb.customer.getIndexes()\n// If an index is incompatible: dropIndex + createIndex (rolling); do NOT use reIndex","note":"После мажорного обновления убедитесь, что FCV достиг целевой версии. Перестроение индексов — не штатный шаг обновления; если индекс упомянут в release notes или вызывает предупреждение в журнале, пересоздайте его через dropIndex + createIndex. В 6.0+ reIndex работает только на standalone и запрещён на членах набора реплик.","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":"После обновления Redis задач компиляции/анализа схемы нет; нужно убедиться, что репликация восстановлена, сохранение RDB/AOF в порядке и число ключей не изменилось.","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":"После обновления убедитесь, что статус кластера 'green' и все шарды размещены. Индексы в формате старше предыдущей мажорной версии в новой версии не читаются, поэтому их нужно перенести в текущий формат через _reindex.","danger":"caution"}}}]},"rosetta/migration.json":{"category":"Миграция и перенос","categoryId":"migration","order":15,"concepts":[{"id":"compare-schema","title":"Сравнить две схемы/среды (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 нет встроенного сравнения схем одной командой. Обычный способ — сгенерировать детерминированный DDL из обеих сред через DBMS_METADATA.GET_DDL (с отключёнными атрибутами STORAGE/сегментов) и сравнить текст через diff. Для визуального/автоматического сравнения используйте мастер Database Diff в SQL Developer или SQLcl.","danger":"safe","readonly":true,"priv":"SELECT_CATALOG_ROLE или доступ на чтение к схеме"},"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":"Проще всего снять вывод pg_dump --schema-only в обеих средах и сравнить через diff. apgdiff и migra — инструменты, которые сразу превращают разницу в исполняемые операторы ALTER. Для детерминированного вывода добавьте --no-owner --no-privileges.","danger":"safe","readonly":true,"priv":"SELECT / чтение схемы (доступ к объектам для 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":"Инструмент mysqldbcompare из MySQL Utilities сообщает о различиях объектов/схем между двумя серверами (в виде применимых операторов при difftype=sql). Способ без инструментов — снять вывод mysqldump --no-data (только схема) или SHOW CREATE TABLE в обеих средах и сравнить через diff. mysqldbcompare уже устарел; для MariaDB/8.x предпочтительны Liquibase или Skeema.","danger":"safe","readonly":true,"priv":"SELECT, SHOW VIEW на обеих схемах"},"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":"Для различий схем SqlPackage /Action:DeployReport возвращает изменения для применения к цели в виде XML-отчёта; /Action:Script генерирует исполняемый скрипт различий. «Schema Compare» в Visual Studio / Azure Data Studio использует тот же движок. Для различий данных на уровне строк есть утилита командной строки tablediff.","danger":"safe","readonly":true,"priv":"VIEW DEFINITION (доступ на чтение к цели для 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 не имеет схемы; фиксированного DDL нет. Сравнение выполняется двумя способами: сравнить правила валидатора $jsonSchema коллекции (через getCollectionInfos) или вывести схему из данных через mongodb-schema / Compass и сравнить распределение полей и типов. Для индексов сравните вывод db.collection.getIndexes().","danger":"safe","readonly":true,"priv":"read (getCollectionInfos, listIndexes)"},"redis":{"command":"# Not applicable: Redis is a schemaless key-value store; there are no\n# table/column definitions (DDL) to compare.\n# Closest equivalent: compare the key space (namespace) structure\n# redis-cli --scan --pattern 'customer:*' | sort > source_keys.txt","note":"Неприменимо: в Redis нет понятия схемы (таблица/столбец/DDL), поэтому классический schema diff не применим. Если нужно структурное сравнение, можно лишь вывести через SCAN шаблон именования ключей (namespace) и сравнить.","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":"В Elasticsearch эквивалент схемы — mapping индекса. Стандартный способ — снять вывод GET <index>/_mapping (и при необходимости _settings) в обоих кластерах и сравнить через diff. Поскольку поля mapping в основном неизменяемы, найденное различие может потребовать нового индекса + _reindex.","danger":"safe","readonly":true,"priv":"view_index_metadata / monitor"}}},{"id":"export-for-migration","title":"Согласованная выгрузка для миграции","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) — стандартный инструмент логической выгрузки для миграции. Для согласованности чтения передайте FLASHBACK_SCN=<scn> (или FLASHBACK_TIME), чтобы все таблицы выгружались согласованно на один SCN; на живой системе это критично. PARALLEL ускоряет выгрузку, а %U создаёт несколько файлов.","danger":"caution","readonly":true,"priv":"DATAPUMP_EXP_FULL_DATABASE, объект DIRECTORY","minver":"10g (Data Pump)"},"postgres":{"command":"# Single compressed file in custom format (restorable in parallel)\npg_dump -Fc -Z6 -f shop.dump shop\n\n# Directory format (parallel dump + parallel restore):\npg_dump -Fd -j4 -f shop_dir shop\n\n# For the whole cluster + roles/tablespaces:\n# pg_dumpall --globals-only > roles.sql","note":"Для миграции предпочтителен формат pg_dump -Fc (custom) или -Fd (directory); оба позволяют выборочную/параллельную загрузку через pg_restore. Так как pg_dump работает в одной транзакции, его вывод автоматически является согласованным снимком. Объекты уровня кластера, такие как роли и табличные пространства, выгружаются отдельно через pg_dumpall --globals-only.","danger":"safe","readonly":true,"priv":"SELECT (на все выгружаемые объекты)"},"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":"В InnoDB --single-transaction даёт единый согласованный снимок и не блокирует таблицы. --source-data=2 (ранее --master-data=2) записывает комментарием в начале вывода позицию binlog/GTID, нужную для запуска репликации/CDC; это необходимо для переключения без простоя. Для больших данных mydumper работает параллельно.","danger":"safe","readonly":true,"priv":"SELECT, LOCK TABLES, RELOAD, REPLICATION CLIENT (для --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":"При миграции на тот же или более новый SQL Server самый надёжный путь — BACKUP ... RESTORE (целостность проверяется через CHECKSUM). Для миграции, независимой от версии/платформы, или переноса в Azure SQL предпочтительна выгрузка схемы+данных в один файл .bacpac через SqlPackage /Action:Export. Для отдельных таблиц используется bcp.","danger":"safe","readonly":true,"priv":"BACKUP DATABASE (db_backupoperator) или доступ на чтение для 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 — стандартная бинарная (BSON) выгрузка для миграции. В наборе реплик опция --oplog захватывает из oplog изменения, записанные во время дампа, обеспечивая согласованную точку во времени (в паре с mongorestore --oplogReplay); опция действует только для полного дампа. Для миграции большого кластера на уровне кластера лучше подходят mongosync/онлайн-инструменты.","danger":"safe","readonly":true,"priv":"read (read на локальной db для 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":"В Redis согласованная выгрузка — это снимок RDB. BGSAVE создаёт согласованный dump.rdb в фоне через fork, не блокируя основной процесс (SAVE блокирует, использовать не следует). Путь к файлу определяется через CONFIG GET dir и dbfilename; для миграции скопируйте этот dump.rdb в каталог данных целевого сервера.","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":"В Elasticsearch согласованный способ выгрузки для миграции — снимок (snapshot). Сначала регистрируется репозиторий (fs, S3, GCS и т. д.), затем делается снимок; снимки инкрементальны и фиксируют согласованное состояние на момент создания. Для небольших данных/одного индекса можно использовать и elasticdump, но стандарт — snapshot.","danger":"safe","readonly":true,"priv":"manage / create_snapshot"}}},{"id":"import-for-migration","title":"Импорт в целевую систему","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 загружает в цель файл .dmp, снятый через expdp. REMAP_SCHEMA переносит источник в другую схему, а REMAP_TABLESPACE — в другое табличное пространство; это часто используется при миграции между средами. Поведение для существующих таблиц задаётся через TABLE_EXISTS_ACTION, скорость — через PARALLEL.","danger":"caution","priv":"DATAPUMP_IMP_FULL_DATABASE, квота на целевое табличное пространство"},"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":"С pg_restore -j дампы формата custom/directory загружаются параллельно (несколькими процессами); для больших баз это заметно ускоряет загрузку. --clean --if-exists сначала удаляет объекты в цели, а --no-owner адаптирует к другой структуре ролей. Простые SQL-дампы загружаются через psql -f (не параллельно).","danger":"caution","priv":"Привилегии CREATE / владельца на целевой базе"},"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":"SQL-дамп mysqldump загружается перенаправлением в клиент mysql (mysql shop < dump.sql). Для параллельной и намного более быстрой загрузки каталог, снятый mydumper, восстанавливается через myloader --threads. Отключение FOREIGN_KEY_CHECKS и UNIQUE_CHECKS на время загрузки повышает производительность.","danger":"caution","priv":"INSERT, CREATE (и DROP для --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":"Бэкап .bak загружается через RESTORE DATABASE; при переносе на другую разметку дисков WITH MOVE перенаправляет файлы данных/журнала на новые пути. Для независимой от версии цели/Azure SQL SqlPackage /Action:Import создаёт из файла .bacpac (схема + данные) новую базу.","danger":"caution","priv":"dbcreator/sysadmin для RESTORE; CREATE DATABASE для 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 загружает в цель BSON-вывод mongodump. --oplogReplay воспроизводит изменения oplog в бэкапе, снятом с --oplog, приводя дамп к единой согласованной точке во времени. Имена баз/коллекций меняются через --nsFrom/--nsTo, существующие данные можно очистить через --drop.","danger":"caution","priv":"readWrite (restore/clusterAdmin для 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 загружает RDB только при запуске. Поэтому целевой сервер останавливают, перенесённый dump.rdb кладут в каталог данных (CONFIG GET dir) и перезапускают сервер; при запуске память заполняется из RDB. Чтобы загрузить без остановки сервера, передайте ключи в формате RESP через 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":"На целевом кластере зарегистрируйте тот же репозиторий снимков, что и на источнике (файлы должны быть доступны), затем восстановите индексы через _snapshot/.../_restore. Имена индексов меняются через rename_pattern/rename_replacement. Существующие индексы нельзя восстановить, пока они открыты; сначала закройте или переименуйте их.","danger":"caution","priv":"manage (create_index, restore)"}}},{"id":"heterogeneous-migration","title":"Миграция на другую СУБД (гетерогенная)","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":"Для гетерогенной миграции с Oracle (особенно на PostgreSQL) самый распространённый открытый инструмент — Ora2Pg: он преобразует таблицы, ограничения, PL/SQL и данные в целевой диалект; --estimate_cost оценивает трудоёмкость миграции. Для онлайн-миграции/миграции с малым простоем используются AWS DMS или Oracle GoldenGate (с CDC).","danger":"caution","readonly":true,"priv":"SELECT на исходную схему (доступ на чтение к представлениям DBA)"},"postgres":{"command":"# MySQL -> PostgreSQL: pgloader (schema+data+type conversion in one command)\npgloader mysql://app_user:password@source/shop \\\n         postgresql://app_user:password@target/shop\n\n# Also from SQLite/MSSQL sources: with a pgloader ./migration.load script\n# For a managed/online PostgreSQL target: AWS DMS","note":"pgloader — стандартный инструмент миграции на PostgreSQL с других движков (MySQL, SQLite, MS SQL): по строкам подключения одной командой создаёт схему, сопоставляет типы и быстро копирует данные; свои преобразования/сопоставления типов задаются в скрипте .load. Если источник — Oracle, предпочтителен Ora2Pg; для онлайн-миграции — AWS DMS.","danger":"caution","priv":"CREATE на цели; SELECT на источнике"},"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":"Для миграции на MySQL с других движков нет одного доминирующего открытого инструмента; самое распространённое решение — AWS DMS (управляемый, с поддержкой CDC) или адаптация дампа источника по синтаксису/типам и загрузка через клиент mysql. MySQL и MariaDB в основном совместимы; вывод mysqldump обычно переносится напрямую.","danger":"caution","priv":"CREATE, INSERT на цели"},"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":"Для миграции с SQL Server на PostgreSQL используется pgloader (источник mssql://); если нужно сохранить T-SQL без изменения приложения, Babelfish for Aurora PostgreSQL предлагает слой совместимости. Инструмент Microsoft SSMA автоматизирует миграцию с других движков (Oracle, MySQL, DB2) на SQL Server. Для онлайн-миграции — AWS DMS.","danger":"caution","priv":"SELECT на источнике; CREATE на цели"},"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":"Миграция между MongoDB и SQL-движками требует преобразования модели строка<->документ (ETL); автоматической единой команды нет. Из SQL в MongoDB MongoDB Relational Migrator сопоставляет таблицы со схемой документов; в простых случаях сгенерируйте CSV/JSON из источника и загрузите через mongoimport. В обратном направлении вывод mongoexport загружается в целевую реляционную таблицу через COPY/LOAD DATA.","danger":"caution","priv":"read на источнике, write на цели"},"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":"В гетерогенной миграции Redis обычно выступает слоем кэша как цель/источник; для загрузки данных в Redis из реляционных или файловых источников используется официальный инструмент Redis RIOT (db-import сопоставляет результат SQL со структурами HSET/SET). Для Redis -> Redis или в файл есть команды riot replicate / file-export.","danger":"caution"},"elasticsearch":{"command":"# Relational/other source -> Elasticsearch: Logstash (JDBC input)\n# logstash.conf: input { jdbc { statement => \"SELECT * FROM customer\" } }\n#                output { elasticsearch { index => \"customer\" } }\n\n# ES -> ES (version/cluster upgrade): reindex from a remote source\nPOST _reindex\n{ \"source\": { \"remote\": { \"host\": \"http://old:9200\" }, \"index\": \"customer\" },\n  \"dest\": { \"index\": \"customer\" } }","note":"Самый распространённый инструмент миграции из других БД в Elasticsearch — Logstash (вход JDBC/file -> выход elasticsearch); используются также собственные коннекторы/Beats Elastic. При переходе со старого кластера Elasticsearch на новый обычен remote reindex (_reindex с источником 'remote'); на цели должен быть задан reindex.remote.whitelist.","danger":"caution","priv":"write/create_index на цели"}}},{"id":"setup-cdc-replication","title":"Настроить непрерывную репликацию / CDC (переключение без простоя)","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":"В Oracle стандартный инструмент CDC без простоя/гетерогенного CDC — Oracle GoldenGate: процесс Extract захватывает изменения из журналов повторного выполнения (redo log), а Replicat применяет их к цели; должно быть включено supplemental logging. Типичная миграция: начальная загрузка через Data Pump, далее GoldenGate передаёт изменения в цель, что позволяет переключиться почти без простоя.","danger":"caution","priv":"привилегии администратора GoldenGate, supplemental logging"},"postgres":{"command":"-- Source (publisher): create a publication (wal_level must be logical)\nCREATE PUBLICATION migration_pub FOR ALL TABLES;\n\n-- Target (subscriber): create a subscription (initial copy + continuous stream)\nCREATE SUBSCRIPTION migration_sub\n  CONNECTION 'host=source dbname=shop user=repl password=***'\n  PUBLICATION migration_pub;","note":"PostgreSQL обеспечивает CDC встроенной логической репликацией: CREATE PUBLICATION на источнике, CREATE SUBSCRIPTION на цели. При запуске подписка копирует существующие данные (initial copy), затем непрерывно передаёт изменения из WAL; требуется wal_level=logical. Для больших/сложных сценариев предпочтительны pglogical или Debezium.","danger":"caution","priv":"роль REPLICATION на источнике; superuser на цели для CREATE SUBSCRIPTION (в PG16+ достаточно и роли pg_create_subscription)","minver":"10 (логическая репликация)"},"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":"В MySQL переключение без простоя выполняется через репликацию на основе binlog. Начальная загрузка снимается через mysqldump --source-data, и фиксируется позиция binlog/GTID; на цели репликация запускается с этой позиции через CHANGE REPLICATION SOURCE TO (синтаксис 8.0.23+; ранее CHANGE MASTER TO). START REPLICA непрерывно применяет изменения. Настройка на основе GTID надёжнее.","danger":"caution","priv":"REPLICATION SLAVE (пользователь repl), REPLICATION CLIENT","minver":"8.0.23 (CHANGE REPLICATION SOURCE)"},"mssql":{"command":"-- Option A: Transactional Replication (publisher->distributor->subscriber) — SSMS wizard\n\n-- Option B: Change Data Capture (track changes as tables)\nEXEC sys.sp_cdc_enable_db;              -- enable CDC at database level\nEXEC sys.sp_cdc_enable_table\n  @source_schema = N'dbo', @source_name = N'customer',\n  @role_name = NULL;                    -- enable CDC at table level\n-- Changes are read from the cdc.dbo_customer_CT table and moved to the target.","note":"В SQL Server два основных пути непрерывной репликации: если нужен прямой поток в цель, настройте Transactional Replication (publisher/subscriber); если изменения будут переноситься собственным ETL, включите Change Data Capture (sp_cdc_enable_db + sp_cdc_enable_table) и читайте из таблиц _CT. Для гетерогенных целей CDC комбинируется с AWS DMS/Debezium.","danger":"caution","priv":"sysadmin (для включения CDC); SQL Server Agent должен быть запущен"},"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":"В MongoDB для CDC используются Change Streams: db.collection.watch() передаёт события insert/update/delete из oplog в реальном времени для применения к цели (требуется набор реплик). Для полной непрерывной миграции/репликации между кластерами официальный инструмент MongoDB Cluster-to-Cluster Sync — mongosync (не только для Atlas; работает и с самостоятельно управляемыми кластерами) — управляет начальной загрузкой + непрерывной синхронизацией.","danger":"caution","priv":"changeStream (read); clusterAdmin для 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":"В Redis переключение без простоя выполняется встроенной репликацией: выполните REPLICAOF <source host port> (ранее SLAVEOF) на целевом сервере; цель сначала получает полный RDB, затем постоянно синхронизируется потоком команд. Проверьте master_link_status:up через INFO replication, а после завершения переключения сделайте цель standalone через 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":"Настоящая функция непрерывной репликации в Elasticsearch — Cross-Cluster Replication (CCR): целевой кластер следует за leader-индексом на источнике и непрерывно получает изменения (требуется коммерческая/Platinum-лицензия). Без лицензии почти непрерывная синхронизация достигается периодическим remote reindex (инкрементально, по полю времени/последовательности).","danger":"caution","priv":"manage/read_ccr для CCR; whitelist для remote reindex"}}},{"id":"verify-migration","title":"Проверка после миграции (число строк/контрольная сумма)","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":"Самая быстрая проверка — сравнить COUNT(*) по каждой таблице на обеих сторонах. Чтобы найти реальные различия содержимого на уровне строк, используйте пакет DBMS_COMPARISON: он построчно сравнивает исходную и целевую таблицы через database link и может показать/устранить различия. Для контрольных сумм дайджест столбцов можно снять через ORA_HASH или STANDARD_HASH.","danger":"safe","readonly":true,"priv":"SELECT на обеих сторонах; EXECUTE на 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":"Первый шаг — сравнить count(*) по каждой таблице. Для проверки содержимого вычислите md5 текстового представления каждой строки, соедините их в отсортированном порядке и снова захэшируйте; это даёт контрольную сумму, не зависящую от порядка, для сравнения двух сторон. Для больших данных берите дайджест только по изменяемым/ключевым столбцам; для воспроизводимости важна сортировка по PK.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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":"В MySQL CHECKSUM TABLE даёт одну контрольную сумму содержимого таблицы; выполните его на источнике и цели и сравните (движок/версия должны совпадать). Для числа строк используйте COUNT(*). В живом продакшене и для согласованности репликации отраслевой стандарт — pt-table-checksum из Percona Toolkit; он сравнивает по чанкам, онлайн и с малым влиянием.","danger":"safe","readonly":true,"priv":"SELECT на таблицу"},"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":"В SQL Server официальная утилита командной строки для поиска различий на уровне строк — tablediff (сообщает, какие строки добавлены/изменены, а с необязательным -f пишет скрипт исправления). Для быстрой общей проверки CHECKSUM_AGG(BINARY_CHECKSUM(*)) даёт один дайджест таблицы; сравнивайте обе стороны вместе с COUNT_BIG(*) (не зависит от порядка строк).","danger":"safe","readonly":true,"priv":"SELECT на обеих таблицах"},"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":"Первая проверка — сравнить число документов на обеих сторонах через countDocuments() (estimatedDocumentCount быстр, но опирается на метаданные; для точного подсчёта используйте countDocuments). Для проверки содержимого команда dbHash создаёт md5-дайджест на уровне коллекции/базы; сравните хэши двух сред. Индексы проверяйте отдельно через getIndexes().","danger":"safe","readonly":true,"priv":"read (доступ для 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":"В Redis базовая проверка — сравнение общего числа ключей через DBSIZE; распределение по базам показывает INFO keyspace. Встроенной контрольной суммы таблиц в Redis нет; если нужна точная проверка содержимого, снимите сериализацию DUMP по каждому ключу и сравните обе стороны (учитывайте различия TTL). Можно также использовать инструмент riot compare.","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":"В Elasticsearch базовая проверка — сравнение числа документов в обоих кластерах через GET <index>/_count (_cat/indices docs.count тоже даёт быструю сводку). Опирайтесь на _count: _cat/indices docs.count считает на уровне Lucene (включая вложенные nested-поддокументы, напр. один документ с двумя nested-элементами считается как 3), поэтому может отличаться от _count. Для проверки содержимого сравнивайте выборку документов по одному набору _id или через поле хэша.","danger":"safe","readonly":true,"priv":"read / monitor на индекс"}}}]},"rosetta/tuning.json":{"category":"Настройка производительности","categoryId":"tuning","order":16,"concepts":[{"id":"plan-baseline","title":"Закрепить / принудительно задать план запроса (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 фиксирует план; самая сильная защита от регрессий. Можно также использовать хинты /*+ ... */.","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":"Альтернатива: управлять на уровне сессии параметрами вроде enable_seqscan; для постоянного закрепления используйте pg_hint_plan.","danger":"caution"},"mysql":{"command":"SELECT /*+ INDEX(customer customer_city_idx) */ * FROM customer WHERE city='Ankara';","note":"Хинты оптимизатора встраиваются в запрос. Для постоянной перезаписи используйте плагин query rewrite.","danger":"safe"},"mssql":{"command":"-- Force the good plan in Query Store:\nEXEC sp_query_store_force_plan @query_id = 42, @plan_id = 17;","note":"Query Store должен быть включён. Альтернатива: OPTION (USE PLAN N'...') или plan guide.","danger":"caution","priv":"ALTER на базу данных"},"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 принудительно задаёт конкретный индекс; устаревший план очищается через planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner.","note":"Производительность определяется структурой данных и сложностью команд.","danger":"safe"},"elasticsearch":{"command":"# There is no plan pinning; optimize the query/index and routing.\nGET <index>/_search { \"profile\": true, \"query\": {...} }","note":"Используйте profile, чтобы увидеть, какой запрос дорогой; управляйте через шарды/routing.","danger":"safe"}}},{"id":"gather-stats-advanced","title":"Сбор статистики с расширенными параметрами (гистограммы и т. п.)","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 управляет гистограммами, degree — параллелизмом. Для фиксации статистики используйте LOCK_TABLE_STATS.","danger":"safe"},"postgres":{"command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500;\nANALYZE customer;","note":"Целевой размер выборки для столбца; default_statistics_target задаёт глобальное значение по умолчанию.","danger":"safe"},"mysql":{"command":"ANALYZE TABLE customer UPDATE HISTOGRAM ON city WITH 32 BUCKETS;","note":"Поддержка гистограмм в MySQL 8.0; полезна для столбцов, по которым фильтруют, но которые неселективны.","danger":"safe"},"mssql":{"command":"UPDATE STATISTICS dbo.customer WITH FULLSCAN;\n-- Filtered statistics: CREATE STATISTICS ... WHERE ...","note":"FULLSCAN — самый точный; на больших таблицах используйте SAMPLE. AUTO_UPDATE_STATISTICS — для автоматического обновления.","danger":"safe"},"mongodb":{"command":"// There are no manual statistics; the planner measures at runtime.\ndb.customer.getPlanCache().clear()","note":"Кроме очистки устаревшего кэша планов, ручная статистика не нужна.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"Статистики/оптимизатора нет.","danger":"safe"},"elasticsearch":{"command":"# There is no statistics gathering; refresh searchability:\nPOST <index>/_refresh","note":"Field data и doc values используются во время запроса; force merge сокращает число сегментов.","danger":"safe"}}},{"id":"perf-report","title":"Сгенерировать отчёт о производительности (AWR / ASH и аналоги)","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 требуют лицензии Diagnostics Pack. Statspack — аналог без лицензии.","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":"Как аналог AWR используйте расширение pg_profile (периодические снимки) или анализ журналов pgBadger.","danger":"safe"},"mysql":{"command":"-- sys schema summaries:\nSELECT * FROM sys.statement_analysis LIMIT 20;\nSELECT * FROM sys.io_global_by_file_by_bytes LIMIT 10;","note":"Схема sys превращает performance_schema в удобочитаемые отчёты; Enterprise Monitor — более полный.","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 хранит планы/производительность во времени; sp_BlitzFirst (от сообщества) даёт быструю диагностику.","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 показывают мгновенную нагрузку; профилировщик фиксирует медленные операции.","danger":"safe"},"redis":{"command":"INFO stats\nredis-cli --latency-history\nSLOWLOG GET 20","note":"--latency и --latency-history дают профиль задержек; INFO — все счётчики.","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 показывает самые дорогие потоки; _nodes/stats даёт общие метрики. Мониторинг (Kibana) — исторический.","danger":"safe"}}},{"id":"optimizer-params","title":"Посмотреть/изменить настройки оптимизатора / параллелизма","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":"Параметры вроде optimizer_index_cost_adj и parallel_degree_policy влияют на выбор плана; меняйте их осторожно.","danger":"caution"},"postgres":{"command":"SHOW random_page_cost;\nSHOW max_parallel_workers_per_gather;\n-- Session: SET enable_seqscan = off;","note":"На SSD рекомендуется random_page_cost ~1.1. Флаги enable_* — для диагностики, а не постоянное решение.","danger":"caution"},"mysql":{"command":"SELECT @@optimizer_switch;\nSET SESSION optimizer_switch='index_merge=off';","note":"optimizer_switch включает/выключает отдельные оптимизации; тестируйте на уровне сессии.","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 — без ограничений; для OLTP обычно 4-8. Значение «cost threshold for parallelism» по умолчанию (5) для большинства слишком мало.","danger":"caution"},"mongodb":{"command":"# There are no optimizer parameters; indexes and query shape are decisive.\ndb.customer.find({...}).explain('allPlansExecution')","note":"allPlansExecution показывает все планы, оценённые планировщиком.","danger":"safe"},"redis":{"command":"# Not applicable.","note":"Планировщика нет; производительность определяют io-threads и выбор структуры данных.","danger":"safe"},"elasticsearch":{"command":"GET <index>/_settings?include_defaults=true\n# search limits such as search.max_buckets, index.max_result_window","note":"Оптимизатора нет; производительность определяют число шардов, routing и структура запроса.","danger":"safe"}}},{"id":"sql-monitor","title":"Мониторинг выполняющихся/недавних SQL в реальном времени","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 показывает долгие/параллельные запросы в реальном времени (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":"С auto_explain планы медленных запросов можно журналировать автоматически.","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 показывает выполняющиеся сейчас операторы.","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» в SSMS показывает выполнение плана в реальном времени.","danger":"safe"},"mongodb":{"command":"db.currentOp({ active: true, secs_running: { $gt: 1 } })","note":"Показывает долгие операции в реальном времени; их можно завершить через killOp.","danger":"safe"},"redis":{"command":"MONITOR","note":"Транслирует ВСЕ команды в реальном времени — влияет на производительность в продакшене, используйте лишь кратко. Предпочтительнее SLOWLOG.","danger":"caution"},"elasticsearch":{"command":"GET _tasks?detailed=true&group_by=parents","note":"Показывает выполняющиеся задачи в реальном времени; долгая задача отменяется через POST _tasks/<id>/_cancel.","danger":"safe"}}}]},"rosetta/tuning-adv.json":{"category":"Продвинутая настройка","categoryId":"tuning-adv","order":17,"concepts":[{"id":"parallel-query","title":"Параллельный запрос / степень параллелизма","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":"Хинт PARALLEL применяет к таблице параллельное сканирование; degree=DOP. Чрезмерный DOP исчерпывает ресурсы сервера, выбирайте его осторожно. parallel_degree_policy=AUTO управляет им автоматически.","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":"Если в плане есть Gather/Parallel Seq Scan, параллелизм работает. Роль играют также max_parallel_workers и min_parallel_table_scan_size.","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 не предлагает общего параллельного выполнения запросов; innodb_parallel_read_threads распараллеливает только сканирование кластерного индекса (напр. COUNT(*)). Сложные JOIN выполняются в одном потоке.","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 ограничивает число параллельных потоков; MAXDOP 1 отключает параллелизм. При превышении «cost threshold for parallelism» план становится параллельным.","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":"В шардированном кластере запрос выполняется на каждом шарде параллельно; на одном узле этапы агрегации в основном последовательны. allowDiskUse разрешает операции, превышающие лимит памяти.","danger":"safe"},"redis":{"command":"# Not applicable: there is no concept of query parallelism.","note":"Выполнение команд однопоточное; масштабируйтесь через Cluster (шардинг) или используйте io-threads для параллелизма сетевого ввода-вывода.","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":"Поиск выполняется одновременно на всех соответствующих шардах; число primary-шардов задаёт базовый уровень параллелизма. slices разделяют большие операции scroll/reindex.","danger":"safe"}}},{"id":"bind-variables","title":"Bind-переменные / 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-переменные сокращают hard parse. Adaptive Cursor Sharing смягчает проблемы sniffing, храня несколько планов для bind-значений с неравномерным распределением. cursor_sharing=FORCE используйте осторожно.","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 выбирает между generic- и custom-планами; force_custom_plan строит план под параметры при каждом выполнении (решение проблем, подобных sniffing).","danger":"safe"},"mysql":{"command":"-- Server-side prepared statement:\nPREPARE stmt FROM 'SELECT * FROM customer WHERE city = ?';\nSET @city = 'Ankara';\nEXECUTE stmt USING @city;\nDEALLOCATE PREPARE stmt;","note":"Prepared statements предотвращают SQL injection и снижают стоимость разбора. MySQL поддерживает параметризацию и на уровне драйвера.","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) строит план под параметры при каждом выполнении (дорого по CPU). OPTIMIZE FOR закрепляет план за конкретным/типичным значением; OPTIMIZE FOR UNKNOWN использует среднюю селективность.","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":"Запросы одной формы (те же поля/операторы) разделяют кэшированный план; разные литеральные значения не создают нового плана. Если распределение значений сильно неравномерно, сбросьте его через planCacheClear.","danger":"safe"},"redis":{"command":"# Not applicable: there is no query planner or parameter sniffing.","note":"Команды выполняются напрямую; производительность определяет проектирование ключей, а не параметризация.","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":"Search template (Mustache) параметризует тело запроса; приложение вызывает фиксированный шаблон с разными значениями, что повышает единообразие запросов и использование кэша.","danger":"safe"}}},{"id":"partition-pruning","title":"Проверить отсечение секций (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 показывают, что читаются только нужные секции (идеально — число, а не KEY). Применение функции/неявного преобразования к ключу секционирования мешает отсечению.","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":"При включённом enable_partition_pruning в плане есть только подходящие секции; остальные помечены 'never executed'. Константы отсекаются при построении плана, параметры — при выполнении.","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":"В MySQL 5.7+ столбец 'partitions' обычного вывода EXPLAIN перечисляет сканируемые секции (синтаксис EXPLAIN PARTITIONS удалён в 8.0); должны быть только нужные секции. Если перечислены все секции, отсечение не происходит.","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":"Функция $PARTITION возвращает номер секции для значения. Если число секций, к которым обращается фактический план выполнения, близко к 1, отсечение работает; если сканируются все секции, предикат не подходит.","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":"Если запрос содержит shard key, mongos направляет его только на нужный шард (targeted); иначе он рассылается на все шарды (scatter-gather). Проверьте задействованные шарды в выводе explain.","danger":"safe"},"redis":{"command":"# Not applicable: there is no table/partition concept.","note":"Данные распределяются через hash slots Cluster; вместо отсечения запросов доступ по ключу прямой.","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 сужает запрос до одного шарда (аналог отсечения). Для данных временных рядов имя индекса с шаблоном даты (sale-2026-01) обеспечивает поиск только в нужных индексах; используйте узкий шаблон вместо wildcard.","danger":"safe"}}},{"id":"result-cache","title":"Кэш результатов / запросов","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":"Server Result Cache хранит результат в SGA; он автоматически инвалидируется при изменении базовых таблиц. RESULT_CACHE_MODE=FORCE применяет его ко всем подходящим запросам, MANUAL — только через хинт. На часто меняющихся таблицах пользы мало.","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 не предлагает кэша результатов запросов; используйте материализованное представление (REFRESH вручную/по расписанию) или кэш на уровне приложения (Redis). Примечание: узел Memoize обеспечивает кэширование строк внутри nested loop, а не кэширование результатов запросов.","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":"Старый query_cache_type полностью удалён в MySQL 8.0 (из-за проблем масштабируемости). Вместо него используйте InnoDB buffer pool, сводные таблицы или внешний кэш (Redis/Memcached).","danger":"safe"},"mssql":{"command":"-- There is no built-in query result cache.\n-- Precompute with an indexed (materialized) view:\nCREATE VIEW dbo.vw_city_summary WITH SCHEMABINDING AS\n  SELECT city, COUNT_BIG(*) c FROM dbo.customer GROUP BY city;\nCREATE UNIQUE CLUSTERED INDEX ix ON dbo.vw_city_summary(city);","note":"SQL Server не хранит кэш результатов (хранит кэш планов). Для повторяющихся агрегаций индексированное представление поддерживается автоматически; автоматическое сопоставление в планах есть только в редакции Enterprise (и Developer), а в Standard к представлению нужно обращаться с хинтом WITH (NOEXPAND); кроме того, buffer pool держит часто читаемые страницы в памяти.","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 не предлагает кэша результатов; WiredTiger держит часто используемые данные в памяти. Для дорогих агрегаций используйте предвычисленную сводную коллекцию через $merge/$out или кэш приложения (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":"Распространённый паттерн cache-aside: если ключ есть, быстро верните его через GET; иначе вычислите из источника и выполните SET с EX (TTL). Redis обычно служит кэшем результатов для других баз.","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":"Shard request cache кэширует результаты по шардам для запросов с size:0 (только агрегации); инвалидируется при refresh индекса. Запросы с size>0 (возвращающие hits) по умолчанию не кэшируются.","danger":"safe"}}}]},"rosetta/advanced-features.json":{"category":"Расширенные возможности","categoryId":"advanced-features","order":18,"concepts":[{"id":"full-text-search","title":"Полнотекстовый поиск","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 (ранее interMedia) обеспечивает полнотекстовый поиск с учётом языка через индекс CONTEXT. Оператор CONTAINS поддерживает операторы поиска вроде AND/OR/NEAR. Индекс поддерживается через CTX_DDL.SYNC_INDEX или автоматический 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 сопоставляет tsvector (документ) с tsquery (запрос) оператором @@. Индекс GIN ускоряет поиск по большим текстам. Языковая конфигурация ('english') выполняет стемминг и обработку стоп-слов.","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":"Индекс FULLTEXT в MySQL запрашивается синтаксисом MATCH ... AGAINST. IN NATURAL LANGUAGE MODE возвращает оценку релевантности; IN BOOLEAN MODE даёт тонкий контроль операторами +/-/*. InnoDB поддерживает полнотекстовый поиск начиная с 5.6.","since":"MySQL 5.6","xnote":"Полнотекстовый поиск в InnoDB появился в MySQL 5.6; до этого он был только в MyISAM.","danger":"safe"},"mssql":{"command":"-- Create a Full-Text Catalog and index:\nCREATE FULLTEXT CATALOG ft_catalog;\nCREATE FULLTEXT INDEX ON dbo.article(content)\n  KEY INDEX PK_article ON ft_catalog;\n-- Search with CONTAINS / FREETEXT:\nSELECT id FROM dbo.article\nWHERE CONTAINS(content, '\"database\" AND \"performance\"');","note":"Для Full-Text Search в SQL Server сначала нужен Full-Text Catalog, затем Full-Text Index на таблице (уникальный KEY INDEX обязателен). CONTAINS поддерживает точный поиск/поиск по близости и логические операторы; FREETEXT — смысловой (лингвистический) поиск. Компонент Full-Text Search должен быть установлен.","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":"Текстовый индекс MongoDB обеспечивает полнотекстовый поиск через оператор $text; у коллекции может быть только один текстовый индекс. Мета-поле textScore даёт релевантность. Для более продвинутого поиска используется Atlas Search ($search, на основе Lucene).","danger":"safe"},"redis":{"command":"# Requires the RediSearch module (Redis Stack).\n# Define the index:\nFT.CREATE article_idx ON HASH PREFIX 1 article: SCHEMA content TEXT\n# Full-text search:\nFT.SEARCH article_idx \"database performance\"","note":"Базовый Redis не предлагает полнотекстового поиска; модуль RediSearch (входит в Redis Stack) обеспечивает индексацию через FT.CREATE и поиск через FT.SEARCH. Поддерживаются стемминг, нечёткий поиск, фильтрация и сортировка. Без установленного модуля функция недоступна.","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 по сути — полнотекстовый поисковый движок на основе Lucene; поля text анализируются при индексации (токенизация, стемминг). Запрос match возвращает результаты, отсортированные по оценке релевантности (_score). Можно назначать языковые анализаторы (напр. 'english').","danger":"safe"}}},{"id":"json-query","title":"Запросы к данным JSON","aliases":["json","json query","json query","jsonb","document query"],"engines":{"oracle":{"command":"-- Extract a scalar value with JSON_VALUE:\nSELECT JSON_VALUE(data, '$.customer.city') AS city FROM orders;\n-- Expand into rows with JSON_TABLE:\nSELECT t.product, t.quantity\nFROM orders s,\n     JSON_TABLE(s.data, '$.items[*]'\n       COLUMNS (product VARCHAR2(50) PATH '$.product',\n                quantity NUMBER PATH '$.quantity')) t;","note":"Oracle (12c+) запрашивает данные JSON через выражения путей SQL/JSON. JSON_VALUE возвращает одно скалярное значение, JSON_QUERY — фрагмент объекта/массива, а JSON_TABLE преобразует JSON в реляционную структуру строк и столбцов. 21c+ поддерживает нативный тип данных JSON.","since":"Oracle 12c","xnote":"Запросы SQL/JSON появились в Oracle 12c, нативный тип данных JSON — в 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":"Тип jsonb в PostgreSQL предлагает богатый набор операторов: -> (объект/элемент json), ->> (текстовое значение), #> (путь), @> (вхождение), ? (наличие ключа). Индекс GIN ускоряет запросы с @> и ?. Возможны и запросы по путям SQL/JSON через jsonb_path_query.","since":"PostgreSQL 9.4","xnote":"Тип json появился в PostgreSQL 9.2, тип jsonb — в 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+) запрашивает нативный тип JSON через JSON_EXTRACT(); оператор -> — сокращение для JSON_EXTRACT, а ->> — для JSON_UNQUOTE(JSON_EXTRACT()). Поиск по полям JSON можно ускорить через generated column + индекс.","since":"MySQL 5.7","xnote":"Нативный тип данных JSON появился в 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+) хранит JSON в столбце NVARCHAR. JSON_VALUE возвращает скалярное значение, JSON_QUERY — объект/массив, а OPENJSON преобразует JSON в табличную форму. ISJSON() проверяет корректность. Поиск по путям JSON можно ускорить через computed column + индекс.","since":"SQL Server 2016","xnote":"Функции JSON (JSON_VALUE/OPENJSON) появились в 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 хранит все данные как документы (JSON-подобный BSON); запросы к ним нативны. Точечная нотация обращается к вложенным полям и элементам массивов. Сложные преобразования выполняются через Aggregation Pipeline, а индексы дают быстрый поиск по вложенным полям.","danger":"safe"},"redis":{"command":"# Requires the RedisJSON module (Redis Stack).\n# Write a JSON document:\nJSON.SET order:1 $ '{\"customer\":{\"city\":\"Ankara\"},\"status\":\"approved\"}'\n# Read a value with JSONPath:\nJSON.GET order:1 $.customer.city","note":"В базовом Redis нет типа JSON; модуль RedisJSON (Redis Stack) обеспечивает чтение и запись на основе JSONPath через JSON.SET/JSON.GET. Вместе с RediSearch документы JSON можно индексировать и запрашивать. Без модуля JSON хранится как строка и разбирается в приложении.","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 нативно хранит и индексирует документы как JSON. Чтобы сохранить границы элементов в массивах вложенных объектов, используйте тип поля 'nested' и запрос nested; иначе массивы типа object «сплющиваются» (риск перекрёстных совпадений). Различие object и nested критично для корректности.","danger":"safe"}}},{"id":"spatial-geo","title":"Географический / пространственный запрос","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 хранит географические данные в типе SDO_GEOMETRY; требуются запись USER_SDO_GEOM_METADATA и пространственный индекс. Операторы вроде SDO_WITHIN_DISTANCE, SDO_RELATE и SDO_NN отвечают на запросы расстояния/пересечения/ближайшего соседа. SRID 4326 — система WGS84 (широта/долгота).","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 превращает PostgreSQL в полнофункциональную пространственную базу. Он предоставляет типы geometry (плоский) и geography (сферический) и сотни функций ST_, таких как ST_Distance/ST_DWithin/ST_Intersects/ST_Contains. Индекс GiST/SP-GiST ускоряет пространственные запросы.","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+) поддерживает соответствующие OGC пространственные типы данных (POINT, POLYGON и т. д.) и функции ST_. Индекс SPATIAL (R-tree) можно создать только в InnoDB/MyISAM и на столбцах NOT NULL; чтобы оптимизатор его использовал, столбец должен быть определён с атрибутом SRID (SRID 4326). С SRID 4326 ST_Distance возвращает геодезическое расстояние в метрах. С POINT(x, y) + ST_SRID x — долгота, y — широта; для ввода WKT (ST_GeomFromText) с SRID 4326 порядок — широта-долгота.","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 предлагает два пространственных типа: geography (сферический, широта/долгота) и geometry (плоский). На запросы отвечают методы вроде STDistance, STIntersects и STContains. Пространственный индекс (на основе сетки) повышает производительность. Учитывайте порядок аргументов 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 хранит географические данные в формате GeoJSON и выполняет сферические запросы с индексом 2dsphere. Поддерживаются операторы $near (сортировка по расстоянию), $geoWithin (внутри области) и $geoIntersects. Координаты — в порядке [долгота, широта]; $maxDistance — в метрах.","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 хранит географические координаты в sorted set через GEOADD (longitude latitude member). GEOSEARCH (Redis 6.2+, заменяет старый GEORADIUS) возвращает члены в пределах радиуса или прямоугольника, отсортированные по расстоянию. GEODIST даёт расстояние между двумя точками. Это базовая функция; модуль не нужен.","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 поддерживает типы полей geo_point (точка) и geo_shape (полигон/линия). Фильтрация выполняется запросами geo_distance (радиус), geo_bounding_box (прямоугольник) и geo_polygon; сортировка — через geo_distance, агрегация — через geohash_grid/geotile_grid. Координаты задаются как lat/lon или массивом [lon, lat].","danger":"safe"}}},{"id":"window-functions-adv","title":"Оконные функции (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 — пионер аналитических (оконных) функций; ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM/AVG OVER, NTILE и FIRST_VALUE полностью поддерживаются. Внутри OVER PARTITION BY задаёт группы, ORDER BY — порядок, а рамка (ROWS/RANGE BETWEEN) — скользящее окно.","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 полностью поддерживает оконные функции со стандартным синтаксисом SQL. Доступны ROW_NUMBER/RANK/DENSE_RANK, LAG/LEAD, агрегаты с OVER, NTILE и FIRST_VALUE/LAST_VALUE. Повторяющиеся определения окон можно именовать и переиспользовать через предложение WINDOW.","since":"PostgreSQL 8.4","danger":"safe"},"mysql":{"command":"-- MySQL 8.0+ window functions:\nSELECT city, name, amount,\n  ROW_NUMBER() OVER (PARTITION BY city ORDER BY amount DESC) AS row_num,\n  RANK()       OVER (PARTITION BY city ORDER BY amount DESC) AS rnk,\n  LAG(amount)   OVER (PARTITION BY city ORDER BY created_at)      AS prev_amount\nFROM sale;","note":"MySQL добавил оконные функции в версии 8.0 (MariaDB 10.2+ их тоже поддерживает). Доступны ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, NTILE и агрегаты с OVER. В версиях до 8.0 этих функций нет; раньше их эмулировали пользовательскими переменными.","since":"MySQL 8.0","xnote":"До MySQL 8.0 оконных функций не было; их эмулировали пользовательскими переменными.","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 поддерживает оконные функции с 2005 (ROW_NUMBER/RANK) и 2012 (LAG/LEAD, рамки, агрегаты с OVER). PARTITION BY, ORDER BY и рамка ROWS/RANGE полностью доступны. Для постраничного вывода обычно используют OFFSET/FETCH или ROW_NUMBER.","since":"SQL Server 2005","xnote":"Ранжирующие функции появились в 2005, LAG/LEAD и рамки — в 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":"Этап агрегации $setWindowFields, появившийся в MongoDB 5.0, обеспечивает оконные функции: $documentNumber (ROW_NUMBER), $rank/$denseRank, $shift (LAG/LEAD), агрегатные функции и скользящие окна (window: { documents/range }). partitionBy и sortBy соответствуют PARTITION BY/ORDER BY в SQL.","since":"MongoDB 5.0","xnote":"Этап $setWindowFields появился в MongoDB 5.0.","danger":"safe"},"redis":{"command":"# Not applicable: there are no SQL-like window functions.","note":"Redis — хранилище ключ-значение; аналитических оконных функций нет. Для упорядоченных результатов можно использовать Sorted Set (доступ по рангу через ZADD/ZRANGE); однако реляционные оконные операции вроде PARTITION BY/LAG выполняются на уровне приложения.","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 не предлагает классических оконных функций (ROW_NUMBER/RANK/LAG). Группировка и упорядочение выполняются через фреймворк агрегаций: агрегация terms близка к PARTITION BY, top_hits — к верхним записям, а derivative/serial_diff (pipeline-агрегации) — к последовательным разностям вроде LAG.","danger":"safe"}}},{"id":"columnstore-analytics","title":"Колоночное хранение / аналитика","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+) ускоряет аналитические запросы, держа таблицы в памяти в колоночном формате (In-Memory Column Store, IM) и сохраняя строковый формат на диске (dual format). INMEMORY — отдельно лицензируемая опция. Параметр inmemory_size задаёт размер области IM.","since":"Oracle 12.1.0.2","xnote":"Колоночное хранилище Database In-Memory появилось в Oracle 12.1.0.2 (отдельная лицензия).","danger":"caution"},"postgres":{"command":"-- Core PostgreSQL is row-based; columnar storage needs an extension:\n-- Citus columnar access method:\nCREATE EXTENSION IF NOT EXISTS citus;\nCREATE TABLE sale_columnar (...) USING columnar;\n-- Alternatives: the Hydra/cstore_fdw extensions.","note":"Базовый PostgreSQL предлагает только строковое (heap) хранение. Для колоночного хранения используются метод доступа 'columnar' в Citus (USING columnar), Hydra или старые расширения cstore_fdw; они дают сжатие и скорость сканирования для аналитики/хранилищ данных.","danger":"safe"},"mysql":{"command":"-- Not applicable: core InnoDB does not offer columnar storage.\n-- For analytics: MySQL HeatWave (cloud, in-memory columnar)\n-- or integration with external ClickHouse/Apache Doris.","note":"Стандартный MySQL (InnoDB) строковый, встроенного columnstore нет. Для ускорения аналитики используется MySQL HeatWave (колоночный in-memory ускоритель в Oracle Cloud); в локальных развёртываниях обычно применяют внешние колоночные движки вроде ClickHouse или 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":"Колоночные индексы (columnstore) SQL Server (некластерный только для чтения в 2012; обновляемый кластерный в 2014; обновляемый некластерный в 2016) хранят данные по столбцам с высоким сжатием; в batch mode дают большое ускорение аналитических запросов с агрегацией/сканированием. Кластерный подходит для хранилищ данных, некластерный — для HTAP.","since":"SQL Server 2012","xnote":"Индекс columnstore появился в 2012 (только чтение), обновляемый кластерный columnstore — в 2014, обновляемый некластерный columnstore — в 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 хранит документы как строкоподобный BSON; встроенного колоночного (columnstore) хранения нет. Для аналитических нагрузок используются выделенные Analytics Nodes в наборе реплик Atlas, Atlas Data Federation или выгрузка данных в колоночную систему (напр. Parquet).","danger":"safe"},"redis":{"command":"-- Not applicable: there is no concept of columnar storage.","note":"Redis — хранилище ключ-значение в памяти; различия строкового/колоночного хранения нет. Аналитические агрегации выполняются специальными структурами вроде Sorted Set, HyperLogLog (приблизительный подсчёт) или RedisTimeSeries; для крупномасштабного 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":"Для сортировки и агрегаций Elasticsearch хранит поля в дисковой колоночной структуре doc_values (по умолчанию включена для полей numeric/keyword/date). Такая колоночная раскладка ускоряет аналитические запросы/агрегации; для полей text по умолчанию отключена.","danger":"safe"}}},{"id":"in-memory","title":"Таблицы в памяти / ускорение","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 ускоряет аналитику, держа часто запрашиваемые таблицы в памяти в колоночном формате (отдельная лицензия). Для OLTP Buffer Cache и пул KEEP также держат часто используемые блоки в памяти. Решение in-memory сохраняет надёжность за счёт строкового формата на диске.","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 не предлагает отдельного движка таблиц в памяти; таблица UNLOGGED пропускает запись в WAL, ускоряя запись (содержимое теряется при сбое; не надёжна). Таблицы TEMP живут в пределах сессии и по возможности держатся в памяти через temp_buffers. Горячие данные кэшируются в 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":"Движок хранения MEMORY (ранее HEAP) в MySQL держит данные таблицы целиком в RAM; очень быстр, но данные теряются при перезапуске сервера (структура сохраняется). Не поддерживает BLOB/TEXT и использует фиксированный размер строки. Для надёжных горячих данных предпочтителен InnoDB buffer pool.","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) обеспечивает высококонкурентный OLTP с таблицами, оптимизированными для памяти (MEMORY_OPTIMIZED=ON), и структурами без латчей. DURABILITY=SCHEMA_AND_DATA сохраняет данные надёжно; SCHEMA_ONLY сохраняет только схему. Требуется файловая группа, оптимизированная для памяти.","since":"SQL Server 2014","xnote":"In-Memory OLTP (Hekaton) появился в 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 нет отдельного типа таблиц в памяти (старый In-Memory Storage Engine был только в Enterprise и объявлен устаревшим). Движок по умолчанию WiredTiger повышает скорость чтения, держа часто используемые данные и индексы в своём кэше в памяти; файловый кэш ОС тоже помогает.","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 держит все данные в RAM; это по замыслу и есть источник его скорости. Сохранность обеспечивают RDB (снимки) и/или AOF (журнал команд). Лимит maxmemory и политика вытеснения (LRU/LFU/random) определяют поведение при заполнении памяти.","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 хранит данные на диске (сегменты Lucene); отдельного движка таблиц в памяти нет. Для скорости он опирается на файловый кэш ОС (page cache), doc_values и архитектуру hot-warm-cold. Достаточный объём RAM и настройки heap критичны для производительности.","danger":"safe"}}},{"id":"time-series","title":"Данные временных рядов","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":"В Oracle данные временных рядов обычно управляются секционированием RANGE (INTERVAL); новые секции создаются автоматически, а старые легко удаляются (отсечение секций ускоряет запросы). Также используются сжатие (ILM/ADO) и материализованные представления для агрегации.","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 может хранить временные ряды с нативным секционированием (PARTITION BY RANGE); расширение TimescaleDB добавляет hypertables с автоматическим разбиением по времени на чанки, сжатием, политиками хранения и continuous aggregates. Распространённое решение для больших нагрузок IoT/метрик.","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 не предлагает специального движка временных рядов; секционирование RANGE (с TO_DAYS/UNIX_TIMESTAMP) позволяет быстро удалять старые данные и отсекать секции. Для временных рядов большого объёма обычно предпочитают внешние решения (TimescaleDB, InfluxDB) или сводные таблицы.","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 управляет временными рядами через секционирование с partition function/scheme; это обеспечивает отсечение и быстрое архивирование данных за большие диапазоны дат. Кроме того, системно-версионные темпоральные таблицы (SYSTEM_VERSIONING) автоматически хранят историю изменений строк и позволяют запросы на момент времени (FOR SYSTEM_TIME).","since":"SQL Server 2016","xnote":"Системно-версионные темпоральные таблицы появились в 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-коллекции, появившиеся в MongoDB 5.0, автоматически хранят данные в сжатом колоночно-подобном (bucketed) формате по времени и мета-полю; сокращают использование диска и индексов и ускоряют запросы по диапазонам. timeField обязателен; metaField (источник/тег) и granularity определяют производительность.","since":"MongoDB 5.0","xnote":"Time-series-коллекции появились в MongoDB 5.0.","danger":"safe"},"redis":{"command":"# Requires the RedisTimeSeries module (Redis Stack).\n# Create a series with a retention period:\nTS.CREATE sensor:1 RETENTION 86400000 LABELS type temperature\n# Add a sample (with an automatic timestamp):\nTS.ADD sensor:1 * 23.5","note":"В базовом Redis нет типа временных рядов; модуль RedisTimeSeries (Redis Stack) обеспечивает высокоскоростной приём отсчётов через TS.CREATE/TS.ADD, автоматическое хранение (RETENTION), даунсэмплинг (TS.CREATERULE) и запросы по диапазонам на основе меток через TS.RANGE/TS.MRANGE. Без модуля временные ряды эмулируются через 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 управляет данными временных рядов через data streams; новые backing-индексы создаются автоматически со временем (rollover), а ILM перемещает старые данные по уровням hot/warm/cold и удаляет их. TSDS (index.mode: time_series) даёт оптимизации хранения, специфичные для метрик; обязательны хотя бы одно поле time_series_dimension, указывающий на него index.routing_path и поле @timestamp (иначе шаблон отклоняется).","danger":"safe"}}},{"id":"vector-search","title":"Векторный поиск (AI/эмбеддинги)","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 предлагает нативный тип данных VECTOR и функцию AI Vector Search. Функция VECTOR_DISTANCE вычисляет сходство по метрикам COSINE/EUCLIDEAN/DOT; для приближённого поиска поддерживаются векторные индексы HNSW и IVF. Эмбеддинги генерируются в базе или внешними моделями.","since":"Oracle 23ai","xnote":"Нативный тип VECTOR и AI Vector Search появились в 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":"Расширение pgvector добавляет в PostgreSQL тип vector и операторы сходства: <-> (L2/евклидово), <=> (косинусное), <#> (скалярное произведение). Индексы HNSW или IVFFlat ускоряют приближённый поиск ближайших соседей (ANN). Широко используется в RAG и семантическом поиске.","xnote":"В базовом PostgreSQL нет векторного типа; требуется расширение pgvector.","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 добавил нативный тип данных VECTOR и функции STRING_TO_VECTOR/VECTOR_TO_STRING и VECTOR_DIM (также в Community). Функция DISTANCE() и масштабный приближённый векторный поиск доступны только в MySQL HeatWave в Oracle Cloud; в Community/Enterprise вычисляйте расстояния в приложении или во внешнем векторном хранилище. Старым локальным версиям нужно внешнее векторное хранилище.","since":"MySQL 9.0","xnote":"Нативный тип данных VECTOR появился в 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 (и Azure SQL) предлагают нативный тип данных vector и функцию VECTOR_DISTANCE (cosine/euclidean/dot); векторные индексы на основе DiskANN для приближённого поиска ближайших соседей в разработке. Эмбеддинги генерируются внешними моделями, например Azure OpenAI, и сохраняются.","since":"SQL Server 2025","xnote":"Нативный тип данных vector появился в 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 запрашивает эмбеддинги этапом агрегации $vectorSearch; внутри выполняется приближённый поиск ближайших соседей на основе HNSW. Векторный индекс определяется как индекс Atlas Search. Функция доступна только в управляемом сервисе Atlas (нет в локальной редакции Community).","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 обеспечивает векторный поиск через модуль RediSearch (Redis Stack); тип поля VECTOR поддерживает индексы FLAT (точный) или HNSW (приближённый) и метрики COSINE/L2/IP. Ближайшие соседи извлекаются синтаксисом KNN внутри FT.SEARCH. Используется для семантического поиска с низкой задержкой и 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 поддерживает запросы приближённого поиска ближайших соседей (HNSW) через тип поля dense_vector и встроенный knn-поиск; сходство может быть cosine/dot_product/l2_norm. Релевантность можно улучшить, комбинируя векторный поиск с классическим текстовым запросом (гибридный поиск / RRF).","since":"Elasticsearch 8.0","xnote":"Встроенный kNN-поиск по dense_vector появился в Elasticsearch 8.x.","danger":"safe"}}},{"id":"cdc-change-tracking","title":"Отслеживание изменений (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 отслеживает изменения на основе журнала повторного выполнения (redo log). LogMiner преобразует redo/архивные журналы в SQL для анализа прошлых DML; анализируемые файлы журналов добавляются через DBMS_LOGMNR.ADD_LOGFILE. Для осмысленных результатов должно быть включено как минимум минимальное supplemental logging (ALTER DATABASE ADD SUPPLEMENTAL LOG DATA). Для непрерывного CDC с низкой задержкой и гетерогенной репликации стандарт — Oracle GoldenGate (отдельный продукт); старые функции Streams/Change Data Capture устарели.","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 превращает записи WAL в читаемый поток изменений через плагин вывода (wal2json, pgoutput, test_decoding); слот репликации гарантирует, что изменения не теряются. wal_level должен быть logical. Debezium использует этот механизм для корпоративного 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":"В MySQL источник CDC — бинарный журнал. При binlog_format=ROW фиксируется каждое изменение строки. Инструменты вроде Debezium или Maxwell читают binlog как реплика и формируют поток событий в Kafka. Включение GTID упрощает надёжное отслеживание позиции.","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 предлагает два механизма: Change Data Capture (CDC) записывает все DML (со старыми/новыми значениями) из журнала транзакций в таблицы изменений и требует SQL Server Agent. Change Tracking лишь легковесно отслеживает, какие строки изменились (итоговые изменения), и не хранит полной истории данных.","since":"SQL Server 2008","xnote":"CDC и Change Tracking появились в 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, построенные на oplog, доставляют события insert, update и delete на уровне коллекции/базы/кластера как поток в реальном времени. Требуется набор реплик или шардированный кластер. С resumeToken поток может продолжиться с места остановки после прерывания; это основа CDC-интеграций.","since":"MongoDB 3.6","xnote":"Change Streams появились в 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":"Keyspace notifications в Redis публикуют события по ключам (SET, DEL, EXPIRE и т. д.) через каналы Pub/Sub; включаются настройкой notify-keyspace-events (K=keyspace, E=keyevent, A=все события). Для надёжного, сохраняемого потока изменений предпочтительны Redis Streams (XADD/XREAD).","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 не предлагает потока захвата изменений (CDC); он нативно не сообщает об изменении документа. Обычный подход — добавить в каждый документ поле updated_ts и выполнять инкрементальные запросы по метке времени (range + sort). Как правило, в Elasticsearch подаётся CDC исходной базы.","danger":"safe"}}}]},"rosetta/sql-syntax.json":{"category":"Синтаксис SQL и ключевые слова","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 определяет именованный временный набор результатов (CTE) внутри запроса; вместо многократного написания подзапроса вы определяете его один раз и ссылаетесь по имени. Разбивает сложные запросы на читаемые части. Поддерживается с 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":"CTE разбивает сложные запросы на именованные блоки. До PostgreSQL 12 CTE был барьером оптимизации (всегда материализовался); начиная с 12 он может встраиваться, а старое поведение задаётся принудительно через 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":"CTE определяет временный именованный набор результатов в запросе и улучшает читаемость. Поддерживается с MySQL 8.0 (и MariaDB 10.2); старым версиям нужна производная таблица (подзапрос в FROM).","since":"MySQL 8.0","xnote":"До MySQL 8.0 CTE не было; то же делалось производной таблицей (подзапросом в 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":"CTE — именованный временный набор результатов. В SQL Server оператор непосредственно перед CTE должен заканчиваться точкой с запятой, поэтому WITH часто пишут как ';WITH'. Действует в пределах одного оператора (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 нет SQL/CTE; ту же задачу решают этапы конвейера агрегации. Каждый этап принимает вывод предыдущего, что даёт пошаговую обработку, похожую на цепочки CTE. Для промежуточных результатов можно также использовать $facet или $lookup.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no concept of SQL or CTEs.","note":"Redis — хранилище ключ-значение без языка запросов. Промежуточные результаты объединяйте на стороне клиента или используйте Lua-скрипт (EVAL) для серверной логики.","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 (API _sql) не поддерживает синтаксис WITH/CTE; группировка и фильтрация пишутся в одном SELECT. При нативном подходе то же делается агрегацией 'terms' и pipeline-агрегацией 'bucket_selector'.","danger":"safe"}}},{"id":"recursive-cte","title":"Рекурсивный 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":"Используется для обхода иерархических (древовидных) данных: оргструктур, спецификаций изделий и т. п. Классический способ Oracle — CONNECT BY / PRIOR и псевдостолбец LEVEL; START WITH задаёт корневую строку. С 11gR2 поддерживается и стандартный рекурсивный CTE.","since":"Oracle 11gR2","xnote":"CONNECT BY — классический метод; стандартный рекурсивный WITH (без ключевого слова RECURSIVE) появился в 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 объединяет якорный запрос и рекурсивную часть через UNION ALL; рекурсивная часть выполняется повторно, пока результат не станет пустым. Идеален для иерархий и обхода графов. Защищайтесь от бесконечных циклов через UNION (вместо ALL) или защиту от циклов.","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 (и MariaDB 10.2+) поддерживают соответствующий стандарту синтаксис WITH RECURSIVE. Глубина рекурсии ограничена системной переменной cte_max_recursion_depth (по умолчанию 1000). В старых версиях приходилось писать цикл в хранимой процедуре.","since":"MySQL 8.0","xnote":"До MySQL 8.0 (MariaDB 10.2) рекурсивных запросов не было; писали цикл в хранимой процедуре.","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":"В SQL Server ключевое слово RECURSIVE для рекурсивного CTE не пишется; CTE с UNION ALL, ссылающийся на себя, автоматически считается рекурсивным. Лимит рекурсии по умолчанию — 100; меняется через OPTION (MAXRECURSION n) (0 = без ограничений, используйте осторожно).","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 нет рекурсивного CTE; для иерархического обхода используется этап агрегации $graphLookup. connectFromField/connectToField задают поля связи, maxDepth — ограничение глубины. Он разрешает древовидные и графовые связи одним запросом.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no recursive queries.","note":"Redis не предлагает языка запросов. Обход иерархий/графов выполняется в коде приложения или на сервере Lua-скриптом; в базовом Redis обход дерева пишется на стороне клиента. (Поддержка ранее использовавшегося модуля RedisGraph прекращена в 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 не предлагает рекурсивных запросов (аналога WITH RECURSIVE / CONNECT BY нет). Иерархические связи моделируются типом поля 'join' (parent/child) или хранением пути в дереве (materialized path) в поле; многоуровневый обход обычно выполняется в приложении.","danger":"safe"}}},{"id":"joins","title":"Типы JOIN (INNER/LEFT/RIGHT/FULL/CROSS)","aliases":["join","inner join","left join","right join","full join","cross join","combine tables","table join"],"engines":{"oracle":{"command":"-- INNER: matching rows\nSELECT m.name, s.amount\nFROM customer m JOIN orders s ON m.id = s.customer_id;\n-- LEFT: customers without orders are included too\nSELECT m.name, s.amount\nFROM customer m LEFT JOIN orders s ON m.id = s.customer_id;\n-- CROSS: Cartesian product\nSELECT r.name, u.name FROM color r CROSS JOIN product u;","note":"JOIN объединяет две таблицы по заданному условию. INNER возвращает только совпадения; LEFT/RIGHT возвращают все строки одной стороны (NULL при отсутствии совпадения); FULL возвращает строки обеих сторон; CROSS — все комбинации. Помимо стандартных ANSI JOIN, Oracle поддерживает и старый синтаксис внешнего соединения (+).","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 полностью поддерживает все стандартные типы JOIN (INNER, LEFT/RIGHT/FULL OUTER, CROSS). USING(my_column) — сокращение для одноимённых столбцов, а NATURAL JOIN автоматически соединяет по всем одноимённым столбцам (используйте осторожно).","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 поддерживают INNER, LEFT, RIGHT и CROSS JOIN, но не предлагают FULL OUTER JOIN напрямую; он эмулируется объединением результатов LEFT и RIGHT через UNION. Старое соединение через запятую даёт декартово произведение и должно ограничиваться через 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 поддерживает все стандартные типы JOIN. Старые операторы внешнего соединения *= / =* удалены; нужно использовать синтаксис ANSI JOIN. APPLY (CROSS/OUTER APPLY) — дополнительная форма соединения для построчных коррелированных подзапросов.","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 нет классического JOIN; этап агрегации $lookup получает совпадающие документы из другой коллекции в виде массива (похоже на left outer join). Для результата как у INNER отфильтруйте пустые массивы через $match, а массив разверните в строки через $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no tables or JOINs.","note":"Redis не предлагает реляционных соединений; данные обычно денормализуются, либо связи строятся на стороне клиента чтением нескольких ключей. Для ограниченных запросов, похожих на соединения, можно использовать RediSearch (FT.AGGREGATE); поддержка модуля RedisGraph прекращена в 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":"Из-за распределённой архитектуры Elasticsearch не предлагает произвольных JOIN между таблицами. Для ограниченных связей есть тип поля 'nested' в пределах одного индекса и тип поля 'join' (parent/child) на одном шарде. Самое распространённое решение — денормализация данных при индексации.","danger":"safe"}}},{"id":"window-functions","title":"Оконные функции (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":"Оконные функции вычисляют значение по «окну» для каждой строки без группировки строк (без сокращения числа строк). PARTITION BY разделяет окно, ORDER BY сортирует его. Oracle — пионер этих аналитических функций и предлагает богатый набор, включая ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD и 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 поддерживает все стандартные оконные функции и позволяет именовать повторяющееся определение окна через WINDOW ... AS. С определениями рамок ROWS/RANGE/GROUPS можно вычислять нарастающие итоги, скользящие средние и т. п.","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 (и MariaDB 10.2+) поддерживают оконные функции; до этого их эмулировали пользовательскими переменными или подзапросами. Доступны ROW_NUMBER, RANK, DENSE_RANK, NTILE, LAG/LEAD, FIRST_VALUE/LAST_VALUE и форма OVER агрегатных функций.","since":"MySQL 8.0","xnote":"До MySQL 8.0 оконных функций не было; их эмулировали переменными/самосоединениями.","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 поддерживает ранжирующие оконные функции с 2005, а агрегаты с рамками и функции вроде LAG/LEAD — с 2012. ROW_NUMBER обычно используется для постраничного вывода (с OFFSET/FETCH) и поиска первых N строк в каждой группе.","since":"SQL Server 2005","xnote":"Ранжирующие оконные функции появились в 2005, рамки и LAG/LEAD — в 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":"Этап $setWindowFields в MongoDB 5.0+ — эквивалент оконных функций SQL: окно задаётся через partitionBy и sortBy, а на выходе получаются $rank/$denseRank/$documentNumber, $shift (LAG/LEAD) и нарастающие итоги. В старых версиях это решалось через $group + код.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no window functions.","note":"Redis не предлагает аналитических оконных функций. Для упорядоченных результатов и ранжирования используйте структуру Sorted Set (ZSET): назначайте оценки через ZADD, читайте ранг через ZRANK/ZREVRANK и диапазоны через ZRANGE. Сложная аналитика выполняется в приложении.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: top_hits and pipeline aggregations\nGET employee/_search\n{\n  \"size\": 0,\n  \"aggs\": { \"dept\": { \"terms\": { \"field\": \"department\" },\n    \"aggs\": { \"top_paid\": { \"top_hits\": {\n      \"sort\": [ { \"salary\": \"desc\" } ], \"size\": 3 } } } } }\n}","note":"В Elasticsearch нет прямых оконных функций. Для первых N записей в группе используйте вложенную агрегацию 'top_hits'; для скользящих/накопительных вычислений — pipeline-агрегации 'derivative', 'cumulative_sum' и 'moving_fn'. Сортировка и разбиение выполняются агрегацией terms.","danger":"safe"}}},{"id":"group-rollup","title":"GROUP BY, HAVING, ROLLUP/CUBE/GROUPING SETS","aliases":["group by","having","rollup","cube","grouping sets","grouping","subtotal","subtotals"],"engines":{"oracle":{"command":"-- Basic grouping + HAVING\nSELECT city, COUNT(*) AS n\nFROM customer\nGROUP BY city\nHAVING COUNT(*) > 5;\n\n-- Subtotals and grand total with ROLLUP\nSELECT city, district, SUM(balance)\nFROM customer\nGROUP BY ROLLUP(city, district);","note":"GROUP BY группирует строки по заданным столбцам и применяет агрегатные функции; HAVING фильтрует сгруппированный результат (WHERE фильтрует строки до группировки). ROLLUP даёт иерархические промежуточные итоги + общий итог, CUBE — все комбинации, GROUPING SETS — выбранные группировки. Oracle поддерживает все три.","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+ поддерживает ROLLUP, CUBE и GROUPING SETS в соответствии со стандартом. Функция GROUPING() показывает, находится ли столбец в итоговой строке (NULL). HAVING фильтрует после агрегации, WHERE — до.","since":"PostgreSQL 9.5","xnote":"ROLLUP, CUBE и GROUPING SETS появились в 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 формируют промежуточные/общие итоги синтаксисом GROUP BY ... WITH ROLLUP; CUBE и GROUPING SETS не поддерживаются. Псевдонимы SELECT можно использовать в HAVING (в Oracle/SQL Server это обычно не работает). В строках ROLLUP сгруппированный столбец отображается как 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":"Наряду с GROUP BY SQL Server поддерживает ROLLUP(), CUBE() и GROUPING SETS (2008+) в современном синтаксисе; старая форма WITH ROLLUP/WITH CUBE устарела. GROUPING() и GROUPING_ID() помогают отличать итоговые строки.","since":"SQL Server 2008","xnote":"Современный синтаксис GROUPING SETS появился в 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":"В MongoDB $group — эквивалент SQL GROUP BY; _id задаёт ключ группировки, а аккумуляторы ($sum, $avg, $max) определяют агрегаты. Фильтрация после группировки выполняется через $match (эквивалент HAVING). Многоуровневые итоги вроде ROLLUP/CUBE получают, запуская несколько конвейеров через $facet.","danger":"safe"},"redis":{"command":"# Not applicable: core Redis has no GROUP BY.\n# Aggregation with RediSearch:\nFT.AGGREGATE customer_idx \"*\" GROUPBY 1 @city REDUCE COUNT 0 AS n","note":"Базовый Redis не предлагает группировки; команда FT.AGGREGATE модуля RediSearch выполняет агрегацию в стиле SQL через GROUPBY и REDUCE (COUNT, SUM, AVG). Шаг FILTER фильтрует сгруппированный результат, как HAVING. Без модуля агрегация выполняется в приложении.","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":"В Elasticsearch группировка выполняется bucket-агрегацией 'terms', а агрегаты — метрическими агрегациями (sum, avg, cardinality). Для фильтрации в стиле HAVING используйте pipeline-агрегацию 'bucket_selector'. Многоуровневые промежуточные итоги строятся вложенными агрегациями.","danger":"safe"}}},{"id":"case-expr","title":"Выражение CASE (условное значение)","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 сопоставляет выражению разные значения в зависимости от условий; есть простая форма (CASE my_column WHEN value) и поисковая (CASE WHEN condition). DECODE в Oracle выполняет похожую задачу, но сравнивает только на равенство и считает NULL равными; для переносимости предпочтителен CASE.","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 полностью поддерживает стандартное выражение CASE, которое можно использовать везде, где вычисляется значение (SELECT, WHERE, ORDER BY, GROUP BY). DECODE в стиле Oracle нет; используйте CASE или COALESCE/NULLIF для коротких условий.","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 поддерживают стандартное выражение CASE; есть также функция IF(condition, if_true, if_false) для быстрого условия с двумя ветвями (похоже на IIF в SQL Server). CASE можно использовать в любом контексте выражений.","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 поддерживает стандартный CASE; с 2012 есть сокращение IIF() для условий с двумя ветвями и CHOOSE() для множественного выбора. IIF фактически транслируется в CASE. CASE можно использовать также в ORDER BY и вычисляемых столбцах.","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":"В агрегации MongoDB эквиваленты CASE — операторы $switch (многоветвевой) и $cond (if-then-else с тремя аргументами). Они используются внутри $project или $addFields для создания условных производных полей.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no in-query conditional expression.","note":"В Redis нет выражения запросов вроде CASE. Условная логика пишется в коде приложения или в серверном Lua-скрипте (EVAL). Ограниченное вычисление выражений возможно через APPLY внутри FT.AGGREGATE в RediSearch.","danger":"safe"},"elasticsearch":{"command":"# Closest equivalent: a condition inside a runtime field or script\nGET employee/_search\n{\n  \"runtime_mappings\": { \"tier\": { \"type\": \"keyword\",\n    \"script\": \"if (doc['salary'].value >= 20000) emit('high'); else if (doc['salary'].value >= 10000) emit('medium'); else emit('low');\" } },\n  \"fields\": [\"tier\"]\n}","note":"В Elasticsearch логика CASE внутри запроса пишется скриптом Painless (runtime field, script_fields или scripted metric). Поток if/else задаётся на языке Painless. Агрегация 'range' тоже может формировать CASE-подобные бакеты для категоризации.","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":"Операторы множеств объединяют наборы результатов двух запросов. UNION удаляет дубликаты (медленнее), UNION ALL сохраняет все (быстрее), INTERSECT возвращает общие строки, а оператор разности — строки, которые есть только в первом наборе. Для разности Oracle использует MINUS вместо стандартного EXCEPT (EXCEPT также есть начиная с 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 полностью поддерживает стандартные UNION, UNION ALL, INTERSECT и EXCEPT (для разности — EXCEPT, а не MINUS). Форма ALL каждого оператора (INTERSECT ALL, EXCEPT ALL) сохраняет количество дубликатов. Число и типы столбцов должны быть совместимы.","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":"Долгое время MySQL поддерживал только UNION / UNION ALL; INTERSECT и EXCEPT добавлены лишь в MySQL 8.0.31 (MariaDB 10.3). В старых версиях используйте INNER JOIN/EXISTS для пересечения и LEFT JOIN ... IS NULL или NOT EXISTS для разности.","since":"MySQL 8.0.31","xnote":"INTERSECT/EXCEPT появились в MySQL 8.0.31 (MariaDB 10.3); до этого решались через 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 поддерживает стандартные UNION, UNION ALL, INTERSECT и EXCEPT (2005+); оператор разности — EXCEPT (не MINUS). INTERSECT/EXCEPT сравнивают строки, считая значения NULL равными.","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":"Этап $unionWith в MongoDB 4.4+ объединяет результаты двух коллекций (как UNION ALL). Чтобы убрать дубликаты, затем выполните дедупликацию через $group. Прямых эквивалентов INTERSECT/EXCEPT нет; они моделируются через $lookup или операторы множеств ($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 нет SQL, но тип данных Set предлагает нативные операции над множествами: SUNION (объединение), SINTER (пересечение), SDIFF (разность) и их варианты STORE, сохраняющие результат. Они работают с заранее построенными членами множеств, а не с результатами запросов.","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 не предлагает UNION/INTERSECT/EXCEPT. Эффект объединения достигается запросом к нескольким индексам через запятую; для пересечения и разности используйте предложения must/should/must_not запроса bool или логику множеств на стороне приложения.","danger":"safe"}}},{"id":"exists-in","title":"EXISTS / IN / NOT EXISTS (подзапрос)","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 проверяет, возвращает ли коррелированный подзапрос хотя бы одну строку (останавливается на первом совпадении, поэтому быстр). IN проверяет, входит ли значение в список/результат подзапроса. Осторожно: если подзапрос может вернуть NULL, NOT IN даёт неожиданные результаты; NOT EXISTS надёжнее. Оптимизатор Oracle часто превращает оба в один и тот же план.","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 полностью поддерживает EXISTS/IN/NOT EXISTS. С NOT IN, если подзапрос содержит NULL, весь результат может оказаться пустым, поэтому рекомендуются NOT EXISTS или LEFT JOIN ... IS NULL. Планировщик в большинстве случаев обрабатывает IN и EXISTS одинаково, как semi-join.","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 поддерживают EXISTS/IN/NOT EXISTS. До MySQL 5.6 коррелированные подзапросы могли быть медленными; 5.6+ ускорил подзапросы IN оптимизациями semi-join и материализации. Ловушка NOT IN + NULL действует и здесь.","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 поддерживает EXISTS/IN/NOT EXISTS, и оптимизатор в основном обрабатывает их как semi/anti-join. Если подзапрос NOT IN содержит NULL, результат может неожиданно оказаться пустым; NOT EXISTS — безопасный выбор. Для похожей логики принадлежности можно использовать и INTERSECT/EXCEPT.","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":"В MongoDB принадлежность списку значений проверяется операторами $in / $nin. Эквивалент EXISTS с подзапросом — получить массив совпадающих документов через $lookup и фильтровать по тому, пуст ли массив. Проверка наличия поля — отдельный оператор { field: { $exists: true } } (наличие поля, а не связь строк).","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 нет подзапросов. Для проверки принадлежности значения используйте SISMEMBER на Set (аналог IN). Команда EXISTS — не подзапрос; она возвращает, существует ли заданный ключ. Реляционные проверки существования выполняются на стороне приложения чтением нескольких ключей.","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":"В Elasticsearch эквивалент IN — запрос 'terms' (совпадение с одним из нескольких значений), а NOT IN — terms внутри bool must_not. Запрос 'exists' не соответствует SQL EXISTS; он проверяет, есть ли у поля значение в документе. Коррелированный EXISTS между таблицами обычно требует денормализации.","danger":"safe"}}},{"id":"limit-paging","title":"Ограничение строк / постраничный вывод (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":"Используется для ограничения/постраничного вывода набора результатов. С Oracle 12c поддерживается стандартный OFFSET n ROWS FETCH NEXT m ROWS ONLY. До этого использовался псевдостолбец ROWNUM; так как ROWNUM назначается до сортировки, обёртка в подзапрос с ORDER BY обязательна.","since":"Oracle 12c","xnote":"До Oracle 12c ограничение строк выполнялось через 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 поддерживает и LIMIT n OFFSET m, и стандартную форму OFFSET ... FETCH. Большие значения OFFSET медленны; для глубокого постраничного вывода эффективнее пагинация по ключу (keyset / 'WHERE id > last_id'). LIMIT без ORDER BY не гарантирует порядок результатов.","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 используют ключевое слово LIMIT в двух формах: LIMIT row_count OFFSET offset или LIMIT offset, row_count. MySQL 8.0.19+ принимает также стандартный синтаксис FETCH. Для производительности при глубоком OFFSET рекомендуется keyset-пагинация.","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 использует TOP n (с вариантами TOP (n) PERCENT / WITH TIES) для первых N строк, а с 2012 — OFFSET ... FETCH для постраничного вывода. Для работы OFFSET/FETCH ORDER BY обязателен. Ключевого слова LIMIT в SQL Server нет.","since":"SQL Server 2012","xnote":"Постраничный вывод через OFFSET/FETCH появился в 2012; до этого он делался через TOP или ROW_NUMBER.","danger":"safe"},"mongodb":{"command":"// Paging with skip/limit\ndb.employee.find()\n  .sort({ salary: -1 })\n  .skip(20)\n  .limit(10)","note":"В MongoDB limit() задаёт число возвращаемых документов, а skip() — число пропускаемых; вместе с sort() они обеспечивают постраничный вывод. Большие значения skip дороги; для глубокого постраничного вывода предпочтителен запрос по диапазону (keyset) от последнего просмотренного значения.","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 нет SQL LIMIT, но есть команды чтения диапазонов: ZRANGE/ZREVRANGE start stop (по индексу) или ZRANGEBYSCORE ... LIMIT offset count для Sorted Set; LRANGE для List. Эти команды дают доступ, похожий на постраничный вывод.","danger":"safe"},"elasticsearch":{"command":"GET employee/_search\n{\n  \"from\": 20,\n  \"size\": 10,\n  \"sort\": [ { \"salary\": \"desc\" } ]\n}","note":"В Elasticsearch 'size' задаёт число возвращаемых hits, а 'from' — число пропускаемых (эквивалент LIMIT/OFFSET). from+size не может превышать index.max_result_window (по умолчанию 10,000); для глубокого постраничного вывода используйте 'search_after' (keyset) или PIT (point-in-time).","danger":"safe"}}},{"id":"merge-upsert","title":"MERGE / UPSERT (обновить, если есть, иначе вставить)","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) сопоставляет источник с целью, обновляя совпадающие строки и вставляя несовпадающие; идеален для массовой синхронизации данных. В Oracle сильная поддержка MERGE: можно писать также WHEN MATCHED ... DELETE и условные обновления. Для источника из одной строки используется таблица dual.","since":"Oracle 9i","danger":"caution"},"postgres":{"command":"-- The PostgreSQL way: INSERT ... ON CONFLICT\nINSERT INTO customer (id, name, balance)\nVALUES (1, 'Ali', 500)\nON CONFLICT (id) DO UPDATE\n  SET name = EXCLUDED.name, balance = EXCLUDED.balance;","note":"Классический upsert в PostgreSQL — INSERT ... ON CONFLICT (уникальный столбец) DO UPDATE / DO NOTHING; новые значения конфликтующей строки доступны через псевдоним EXCLUDED. С PostgreSQL 15 поддерживается и стандартный оператор MERGE. ON CONFLICT атомарен на одной таблице и широко используется.","since":"PostgreSQL 15","xnote":"Стандартный оператор MERGE появился в PostgreSQL 15; до этого upsert выполнялся через 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 используют для upsert INSERT ... ON DUPLICATE KEY UPDATE; при конфликте уникального или первичного ключа строка обновляется. Новые значения доступны через VALUES(my_column) (или псевдоним строки 'new.my_column' в 8.0.19+). INSERT IGNORE молча пропускает строку при конфликте. Стандартного MERGE в MySQL нет.","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 поддерживает оператор MERGE с 2008; он предлагает дополнительные ветви, например WHEN NOT MATCHED BY SOURCE для удаления лишних строк в цели. Оператор MERGE должен завершаться точкой с запятой. Примечание: из-за некоторых ошибок в граничных случаях для простых upsert могут быть предпочтительны отдельные операторы INSERT/UPDATE.","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":"В MongoDB upsert — нативная опция: если добавить { upsert: true } к updateOne/updateMany/findOneAndUpdate, совпадающий документ обновляется, иначе создаётся новый. Для массовой синхронизации ближе к SQL MERGE этап агрегации $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":"В Redis команды записи по своей природе являются upsert: SET/HSET обновляют ключ или поле, если они есть, иначе создают. Опция SET ... NX — запись, только если ключа нет, XX — только если он есть. Различие «обновить, если есть, иначе вставить» уже является поведением большинства команд по умолчанию.","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":"В Elasticsearch вызов index (PUT) с тем же _id полностью заменяет документ, если он есть, иначе создаёт его (аналог upsert). Для частичных обновлений со вставкой при отсутствии используйте 'doc_as_upsert': true или блок 'upsert' в API _update. Возможны и скриптовые upsert.","danger":"caution"}}},{"id":"returning-output","title":"Вернуть изменённую строку (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 возвращает значения строк, изменённых INSERT/UPDATE/DELETE (напр. автоматически сгенерированный ID), без отдельного SELECT. В Oracle одна строка возвращается через RETURNING ... INTO в bind-переменную, несколько строк — через RETURNING ... BULK COLLECT INTO в коллекцию.","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":"Предложение RETURNING в PostgreSQL очень гибкое: оно возвращает любой столбец (включая вычисляемые выражения) строк, затронутых операторами INSERT, UPDATE, DELETE и MERGE, сразу как набор результатов. Это самый чистый способ получить сгенерированные значения serial/identity.","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 не поддерживает стандартный RETURNING; автоинкрементный ключ, сгенерированный INSERT, получают через LAST_INSERT_ID(), а обновлённые значения читают отдельным SELECT. MariaDB предлагает INSERT ... RETURNING с 10.5 и DELETE ... RETURNING с 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 — предложение OUTPUT: оно обращается к псевдотаблицам INSERTED (новые значения) и DELETED (старые значения); в UPDATE можно использовать обе. С OUTPUT ... INTO результаты можно записать в таблицу/табличную переменную. Идеально для копирования изменений в таблицу аудита.","since":"SQL Server 2005","xnote":"Предложение OUTPUT появилось в 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":"В MongoDB команды findOneAndUpdate / findOneAndDelete / findOneAndReplace возвращают затронутый документ в одной атомарной операции. returnDocument: 'before' даёт версию до изменения, 'after' — после. insertOne возвращает _id вставленного документа.","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 нет отдельного RETURNING, но многие команды записи сразу возвращают результат: INCR/INCRBY — новое значение, SET ... GET — старое значение (GETSET устарел с 6.2), LPOP/RPOP — удалённый элемент. Для атомарного чтения-записи такое поведение заменяет 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":"В Elasticsearch операции index/update возвращают метаданные результата (_id, _version, result). Чтобы получить тело обновлённого документа, добавьте '_source': true в запрос _update; поле get ответа содержит текущий _source. Отдельный вызов GET не нужен.","danger":"safe"}}},{"id":"lateral-apply","title":"LATERAL / CROSS APPLY (построчный подзапрос)","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 позволяет подзапросу в FROM ссылаться на столбцы таблицы слева, поэтому можно писать коррелированные соединения вроде «первые N строк для каждой строки». Oracle 12c поддерживает и синтаксис ANSI LATERAL, и CROSS/OUTER APPLY.","since":"Oracle 12c","xnote":"LATERAL и CROSS/OUTER APPLY появились в 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":"В PostgreSQL подзапросы, зависящие от строки слева, пишутся с ключевым словом LATERAL (JOIN LATERAL / CROSS JOIN LATERAL / LEFT JOIN LATERAL). Очень удобно для «первых N в группе» и развёртывания строк функциями, возвращающими множества. Синтаксиса CROSS APPLY нет; используется LATERAL.","since":"PostgreSQL 9.3","xnote":"LATERAL появился в 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":"С MySQL 8.0.14 поддерживаются производные таблицы LATERAL; подзапрос может ссылаться на столбцы внешней таблицы. До этого, а также для CROSS APPLY, эквивалента не было; «первые N в группе» решались оконными функциями или коррелированными подзапросами. В MariaDB LATERAL пока нет.","since":"MySQL 8.0.14","xnote":"Производные таблицы LATERAL появились в MySQL 8.0.14; в MariaDB их нет.","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":"Эквивалент LATERAL в SQL Server — оператор APPLY: CROSS APPLY (как INNER JOIN LATERAL, отбрасывает несовпадения) и OUTER APPLY (как LEFT JOIN LATERAL, NULL для несовпадений). Обычно используется для вызова табличных функций для каждой строки и запросов «первые N в группе».","since":"SQL Server 2005","xnote":"Оператор CROSS/OUTER APPLY появился в 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":"В MongoDB эквивалент LATERAL/APPLY — форма $lookup с вложенным конвейером и переменными let: поля внешнего документа передаются во вложенный конвейер через let как $$variable, где выполняются сопоставление/сортировка/ограничение. Так решается паттерн «связанные первые N для каждого документа».","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no subqueries / LATERAL.","note":"Redis не предлагает построчных коррелированных подзапросов. Паттерн «связанные первые N для каждого элемента» решается циклом на стороне приложения (напр. отдельный ZRANGE на каждый ключ) или серверным Lua-скриптом.","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 нет LATERAL/APPLY; паттерн «первые N записей в группе» достигается вложенной агрегацией top_hits внутри агрегации terms. Вместо коррелированного подзапроса это группировка плюс выборка внутри группы по одному индексу.","danger":"safe"}}},{"id":"pivot","title":"PIVOT / UNPIVOT (строки↔столбцы)","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 превращает различные значения в строках в столбцы (перекрёстная таблица), а UNPIVOT — столбцы обратно в строки; используется в сводных/отчётных таблицах. Oracle предлагает встроенные операторы PIVOT/UNPIVOT с 11g; список столбцов должен быть статическим (динамические столбцы требуют PIVOT XML или кода).","since":"Oracle 11g","xnote":"Встроенные операторы PIVOT/UNPIVOT появились в 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 нет встроенного оператора PIVOT. Есть два способа: функция crosstab() расширения tablefunc или переносимая условная агрегация (SUM(...) FILTER (WHERE ...) или с CASE). Предложение FILTER — элегантная техника pivot, специфичная для PostgreSQL.","danger":"safe"},"mysql":{"command":"-- There is no PIVOT; conditional aggregation with manual CASE\nSELECT product,\n  SUM(CASE WHEN month = 'Jan' THEN amount ELSE 0 END) AS jan,\n  SUM(CASE WHEN month = 'Feb' THEN amount ELSE 0 END) AS feb,\n  SUM(CASE WHEN month = 'Mar' THEN amount ELSE 0 END) AS mar\nFROM sale\nGROUP BY product;","note":"В MySQL/MariaDB нет операторов PIVOT/UNPIVOT; перекрёстная таблица строится вручную через GROUP BY плюс SUM(CASE WHEN ... THEN ... END) для каждого целевого столбца. Если столбцы динамические, запрос генерируется строкой через 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 нативно поддерживает операторы PIVOT и UNPIVOT с 2005. Список столбцов PIVOT задаётся статическими значениями в квадратных скобках; для динамических столбцов запрос строится динамическим SQL. Для небольших сводок альтернатива — условная агрегация (CASE).","since":"SQL Server 2005","xnote":"Операторы PIVOT/UNPIVOT появились в 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 нет оператора PIVOT; перекрёстная таблица строится на этапе $group через $sum + $cond (условная агрегация) для каждого целевого столбца. В обратном направлении (UNPIVOT) поля разворачиваются в строки через $objectToArray и $unwind.","danger":"safe"},"redis":{"command":"# Not applicable: Redis has no pivot/cross tab.","note":"В Redis нет понятия поворота строк и столбцов. Сводки/перекрёстные таблицы вычисляются на стороне приложения; часто запрашиваемые сводки можно предвычислить и хранить в полях Hash (одно поле на столбец). RediSearch FT.AGGREGATE умеет групповую агрегацию, но это не настоящий 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 нет оператора PIVOT; перекрёстная сводка строится вложенными агрегациями terms (внешний ключ / внутренний ключ), результат возвращается вложенными бакетами, а не столбцами, и превращается в таблицу на клиенте. Задание transform может создать сгруппированный целевой индекс, похожий на pivot.","danger":"safe"}}},{"id":"coalesce-null","title":"Обработка NULL (COALESCE/NVL/NULLIF/IS NULL)","aliases":["coalesce","nvl","nullif","is null","ifnull","isnull","null handling","empty value"],"engines":{"oracle":{"command":"-- COALESCE: returns the first non-NULL value (standard)\nSELECT name, COALESCE(phone, mobile, 'none') AS contact FROM customer;\n\n-- Oracle-specific NVL / NVL2\nSELECT NVL(balance, 0) FROM customer;\nSELECT NVL2(discount, 'yes', 'no') FROM orders;\n\n-- NULLIF and IS NULL\nSELECT NULLIF(a, b) FROM t;   -- NULL if a=b\nSELECT name FROM customer WHERE phone IS NULL;","note":"Для обработки NULL (неизвестного значения): COALESCE возвращает первое не-NULL значение в списке (стандарт, несколько аргументов). NVL(a,b) в Oracle — эквивалент с двумя аргументами; NVL2 возвращает разные значения в зависимости от того, NULL ли значение. NULLIF(a,b) возвращает NULL, если они равны. Всегда проверяйте NULL через IS NULL / IS NOT NULL (= NULL не работает).","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 предлагает стандартные COALESCE, NULLIF и IS NULL/IS NOT NULL; вместо NVL из Oracle используйте COALESCE. Для сравнения с NULL используйте IS, а не =; если два NULL нужно считать равными, подойдёт оператор 'IS NOT DISTINCT FROM'.","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 поддерживают стандартные COALESCE и NULLIF; IFNULL(a,b) — сокращение с двумя аргументами (похоже на NVL в Oracle). Оператор <=> обеспечивает NULL-безопасное равенство (считает два NULL равными). NULL проверяется через 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 предлагает стандартный COALESCE (несколько аргументов, с повышением типа данных) и ISNULL(a,b) с двумя аргументами; они тонко различаются (COALESCE стандартен и гибче в приоритете типов). Поддерживаются также NULLIF и IS NULL. ISNULL — эквивалент NVL в Oracle (названия похожи).","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":"В агрегации MongoDB эквивалент COALESCE — оператор $ifNull (несколько аргументов, возвращает первый, который не null/не отсутствует). В запросе { field: null } совпадает и с документами со значением null, и с документами, где поля нет вовсе; явно различайте их через { field: { $type: 'null' } } или $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 нет NULL в смысле SQL; эквивалент — отсутствующий ключ или отсутствующее поле Hash, и в этом случае команды возвращают nil. Логика значения по умолчанию (как COALESCE) применяется на стороне приложения проверкой на nil. Наличие поля проверяется через 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":"В Elasticsearch null или отсутствующие поля проверяются запросом 'exists' (must_not exists = документы без поля). Для значения по умолчанию в стиле COALESCE задайте 'null_value' в mapping или присвойте значение отсутствующим записям параметром 'missing' в агрегациях. Значения по умолчанию можно также получать через runtime fields/скрипты.","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":"Task API возвращает выполняющиеся сейчас задачи (search, reindex, bulk). Для мгновенной нагрузки CPU hot_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":"Вместо блокировок медленность обычно вызывают долгие задачи (большие поиски, reindex). Оценивайте длительность по полю running_time_in_nanos.","danger":"safe"},"kill-session":{"command":"POST _tasks/<task_id>/_cancel\n# task_id format: nodeId:taskNumber (e.g. oTUltX4IQMOUUVeiohTt8A:12345)","note":"Возьмите значение task_id из вывода GET _tasks. Остановить можно только отменяемые (cancellable:true) задачи.","danger":"danger"},"connection-count":{"command":"GET _nodes/stats/http","note":"current_open даёт открытые сейчас HTTP-подключения, total_opened — общее число подключений с момента запуска узла. Верхний предел HTTP-подключений определяется лимитами операционной системы/сокетов, а не 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 даёт общее использование диска всем кластером через indices.store.size_in_bytes. _cat/indices выводит размер каждого индекса в удобочитаемом виде.","danger":"safe"},"largest-tables":{"command":"GET _cat/indices?v&s=store.size:desc&h=index,docs.count,store.size","note":"В Elasticsearch эквивалент «таблицы» — индекс. s=store.size:desc выводит самые большие индексы наверх.","danger":"safe"},"free-space":{"command":"GET _cat/allocation?v","note":"Столбцы disk.used, disk.avail и disk.percent дают использование диска по каждому узлу. При превышении порогов watermark (85%/90%/95%) размещение шардов останавливается.","danger":"safe"},"row-count":{"command":"GET <index>/_count\n# Shortcut: GET _cat/count/<index>?v","note":"_count быстро возвращает число документов. Для подсчёта с фильтром передайте запрос в теле: 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 нет центральной таблицы медленных запросов; search slowlog записывает запросы, превышающие порог, в файлы журналов узла. Читайте журналы там.","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 разбивает время каждого компонента запроса и шага агрегации. _validate/query?explain показывает эквивалент запроса в Lucene и причину отклонения.","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":"Вместо неиспользуемых индексов полей выявляйте через _stats/search индексы с малым числом поисков (search.query_total) или которые больше не запрашиваются, и закрывайте/удаляйте их.","danger":"safe"},"cache-hit-ratio":{"command":"GET _nodes/stats/indices/query_cache\n# Ratio = hit_count / (hit_count + miss_count)","note":"query_cache кэширует результаты в filter context. request_cache исследуйте отдельно через 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":"В Elasticsearch эквивалент логического дампа — snapshot API. wait_for_completion=true ждёт завершения операции; для больших снимков оставьте false и запрашивайте статус отдельно.","danger":"safe"},"restore-dump":{"command":"POST _snapshot/<repo>/<snapshot_1>/_restore\n{\n  \"indices\": \"*\",\n  \"rename_pattern\": \"(.+)\",\n  \"rename_replacement\": \"restored_$1\"\n}","note":"Чтобы восстановить существующий индекс, сначала закройте его (POST <index>/_close) или восстановите под новым именем через rename_pattern. Восстанавливать поверх открытого индекса нельзя.","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":"Поле state принимает значения SUCCESS / IN_PROGRESS / PARTIAL / FAILED. _status даёт подробный прогресс по шардам.","danger":"safe"},"list-users":{"command":"GET _security/user","note":"Должна быть включена X-Pack Security. Для одного пользователя: GET _security/user/<user>. Встроенные пользователи тоже отображаются в списке.","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":"Авторизация основана на ролях: сначала определите роль с привилегиями на индексы/кластер, затем назначьте её пользователю. Пароль должен быть не короче 6 символов.","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":"Роли пользователя смотрите через _security/user, а привилегии роли — через _security/role. _has_privileges проверяет, разрешено ли конкретное действие.","danger":"safe"},"change-password":{"command":"POST _security/user/<user>/_password\n{\n  \"password\": \"NewStrongPassword123\"\n}","note":"Чтобы сменить собственный пароль, достаточно POST _security/user/_password (без имени пользователя). Для встроенного пользователя elastic можно также использовать утилиту bin/elasticsearch-reset-password.","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 основан на сегментах; отдельный шаг обновления статистики не нужен. _refresh сразу делает доступными для поиска ещё не доступные документы (по умолчанию это происходит автоматически каждую 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 используется для изменения mapping или пересоздания повреждённого индекса. Для бесшовного переключения направьте алиас на новый индекс.","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":"Удалённые/обновлённые документы остаются в сегментах как tombstones; forcemerge возвращает это пространство. На активно записываемых индексах операция дорогая; выполняйте её вне часов пик.","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":"Корневой endpoint GET / возвращает версию, версию Lucene и имя кластера. jvm.uptime_in_millis — время работы узла.","danger":"safe"},"show-config-param":{"command":"GET _cluster/settings?include_defaults=true&flat_settings=true","note":"include_defaults=true показывает и значения по умолчанию. Параметры persistent и transient возвращаются в отдельных разделах; для параметров уровня узла используйте GET _nodes/settings.","danger":"safe"},"list-tables":{"command":"GET _cat/indices?v","note":"В Elasticsearch эквивалент реляционной таблицы — индекс. Даёт сводку со столбцами health, status, docs.count и store.size. Вывод по умолчанию перечисляет открытые и закрытые индексы, но не скрытые (напр. backing-индексы .ds-, системные индексы); добавьте &expand_wildcards=all, чтобы включить их, или &expand_wildcards=open — только для открытых.","danger":"safe"},"describe-table":{"command":"GET <index>/_mapping","note":"Mapping даёт типы полей и параметры анализа; это эквивалент DESCRIBE TABLE. Для одного поля: GET <index>/_mapping/field/<field>.","danger":"safe"},"list-indexes":{"command":"GET <index>/_mapping","note":"В ES поля по умолчанию индексируются; отдельной таблицы вторичных индексов нет. Поля с \"index\": false в mapping недоступны для поиска. Для индексов (таблиц) по всему кластеру: 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":"После создания индекса число primary-шардов нельзя изменить на месте (число реплик динамическое); при необходимости создайте новый индекс через API _split / _shrink или через _reindex. Тип существующего поля изменить нельзя; новые поля можно добавлять.","danger":"caution"},"show-ddl":{"command":"GET <index>","note":"Один вызов возвращает settings, mappings и aliases; вместе эти три — эквивалент «DDL» индекса. Тот же вывод можно использовать как тело нового 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 обобщает состояние шардов как green/yellow/red; yellow = реплика не назначена. active_shards_percent даёт долю завершения.","danger":"safe"},"list-replicas":{"command":"GET _cat/shards?v&h=index,shard,prirep,state,node\n# For node roles: GET _cat/nodes?v","note":"Столбец prirep принимает значение p (primary) или r (replica); показывает распределение шардов по узлам и их состояние (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":"При отказе узла Elasticsearch автоматически перевыбирает master и переназначает primary-шарды. Ручной перехват нужен лишь для отсоединения follower-индекса CCR от leader и перевода его в режим записи.","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":"Запись в один документ атомарна и версионирована (оптимистичная конкурентность: if_seq_no / if_primary_term). Понятия отслеживания открытых транзакций нет.","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":"Работает через REST; вместо отдельного CLI-клиента используется curl или Kibana Dev Tools. -k пропускает проверку самоподписанного TLS-сертификата (в продакшене используйте CA).","danger":"safe"},"list-databases":{"command":"GET _cat/indices?v\n# For aliases (logical views): GET _aliases","note":"В Elasticsearch нет отдельного уровня «базы данных»; ближайшие понятия — индексы и группирующие их алиасы. Для 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":"Команды в стиле USE <db> нет; целевой индекс/алиас указывается в пути каждого запроса. Для поиска по нескольким индексам перечислите их через запятую: GET log-2024,log-2025/_search.","danger":"safe"}}},"errors/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00001","title":"Нарушено ограничение уникальности","severity":"caution","meaning":"Вставляемая или обновляемая строка нарушает ограничение UNIQUE или PRIMARY KEY. Иными словами, такое значение уже существует.","cause":"Повторная вставка строки с тем же значением ключа, ввод дублирующихся данных в столбец, который должен быть уникальным, или повторная обработка той же записи при репликации/перезапусках.","fix":"Определите нарушенный столбец по имени ограничения из сообщения об ошибке: SELECT column_name FROM all_cons_columns WHERE constraint_name = 'CONSTRAINT_NAME'. Для поиска дубликатов используйте GROUP BY ... HAVING COUNT(*) > 1. В приложении для существующих записей используйте UPDATE или MERGE вместо INSERT. Если значение последовательности отстало, продвиньте его с помощью ALTER SEQUENCE.","tags":["unique","uniqueness","constraint","unique constraint","primary key","duplicate","duplicate key","insert"]},{"code":"ORA-00054","title":"Ресурс занят, а захват запрошен с NOWAIT, или истёк тайм-аут","severity":"caution","meaning":"Блокируемый ресурс (строка или таблица) заблокирован другой сессией и не может быть захвачен немедленно, поскольку указан NOWAIT или истёк DDL_LOCK_TIMEOUT.","cause":"Обычно это попытка DDL (ALTER TABLE, DROP, CREATE INDEX), пока таблица заблокирована другой транзакцией, либо встреча с заблокированной строкой при SELECT ... FOR UPDATE NOWAIT.","fix":"Найдите сессию, удерживающую блокировку: SELECT * FROM v$locked_object. Соедините v$session и v$lock, чтобы определить блокирующую сессию. Дождитесь завершения транзакции или при необходимости завершите её через ALTER SYSTEM KILL SESSION. Для DDL можно задать время ожидания: ALTER SESSION SET DDL_LOCK_TIMEOUT = 60; Выполняйте DDL в спокойные окна обслуживания.","tags":["lock","locked","ddl","nowait","busy","resource busy","timeout"]},{"code":"ORA-00060","title":"Обнаружена взаимоблокировка при ожидании ресурса","severity":"danger","meaning":"Две или более сессии оказались во взаимоблокировке, каждая ждёт ресурс, удерживаемый другой. Oracle обнаруживает цикл, выбирает жертву и откатывает оператор этой сессии.","cause":"Захват блокировок строк в разном порядке, перекрёстное обновление одних и тех же строк разными транзакциями, блокировки таблицы при обновлении родительской таблицы из-за неиндексированных внешних ключей или параллельный DML над bitmap-индексом.","fix":"Изучите trace-файл взаимоблокировки (граф), на который ссылается alert log, чтобы найти конфликтующие SQL-операторы и строки. В приложении всегда обращайтесь к таблицам/строкам в одном и том же порядке. Индексируйте столбцы внешних ключей (неиндексированные FK — классическая причина взаимоблокировок). Делайте транзакции короткими и пересмотрите частоту commit. Перехватывайте в приложении транзакцию, попавшую во взаимоблокировку, и повторяйте её.","tags":["deadlock","deadlock detected","lock","locking","tx","transaction"]},{"code":"ORA-01017","title":"Неверное имя пользователя/пароль; вход запрещён","severity":"caution","meaning":"Вход отклонён, поскольку имя пользователя или пароль, указанные при подключении к базе данных, неверны.","cause":"Неверный пароль, неверное имя пользователя, настройка чувствительности пароля к регистру (SEC_CASE_SENSITIVE_LOGON), отличающаяся от ожидаемой, изменённый пароль или устаревшие учётные данные в строке подключения приложения.","fix":"Проверьте имя пользователя и пароль. При необходимости сбросьте пароль от имени DBA: ALTER USER app_user IDENTIFIED BY new_password. Убедитесь, что пользователь существует и не заблокирован: SELECT username, account_status FROM dba_users. При версиях хеша паролей 12c+ у клиентов 11g могут возникать проблемы с подключением; проверьте настройку SQLNET.ALLOWED_LOGON_VERSION_SERVER.","tags":["auth","credentials","password","password","logon","login","privilege"]},{"code":"ORA-01555","title":"Snapshot too old: слишком маленький номер сегмента отката","severity":"danger","meaning":"Длительный запрос не смог получить старые данные undo, необходимые для построения согласованного по чтению образа на момент его начала, поскольку эта информация undo была перезаписана.","cause":"Маленькое табличное пространство undo, короткий UNDO_RETENTION, длительные запросы при очень интенсивном параллельном DML или цикл, выполняющий commit по той же таблице внутри запроса (fetch across commit).","fix":"Увеличьте UNDO_RETENTION и расширьте табличное пространство undo; по возможности оставьте его под автоматическим управлением (AUTOEXTEND on). Разбейте длинные запросы на более мелкие части или выполняйте их вне пиковых часов. Рассмотрите RETENTION GUARANTEE: ALTER TABLESPACE undotbs1 RETENTION GUARANTEE. Исправьте логику приложения, выполняющую commit внутри цикла.","tags":["undo","snapshot","rollback","read consistency","consistency","long query"]},{"code":"ORA-01652","title":"Не удаётся расширить временный сегмент на N в табличном пространстве","severity":"danger","meaning":"Oracle не нашёл достаточно свободного места в табличном пространстве для расширения временного (temp) сегмента. Обычно возникает при сортировках, hash join или операциях с глобальными временными таблицами.","cause":"Табличное пространство TEMP заполнено; крупные операции ORDER BY, GROUP BY, DISTINCT, hash join или создания индексов не помещаются в память PGA и сбрасываются на диск; у файлов данных отключён AUTOEXTEND или заканчивается место на диске.","fix":"Отслеживайте использование TEMP: SELECT * FROM v$sort_usage / v$tempseg_usage. Добавьте файл в табличное пространство TEMP или увеличьте его: ALTER TABLESPACE temp ADD TEMPFILE '...' SIZE 2G AUTOEXTEND ON. Если заполнено постоянное табличное пространство, добавьте файл данных. Сократите дисковые сортировки за счёт оптимизации запроса (подходящий индекс, больший PGA_AGGREGATE_TARGET).","tags":["temp","tablespace","free space","space","sort","sorting","extend"]},{"code":"ORA-04031","title":"Не удаётся выделить N байт разделяемой памяти","severity":"danger","meaning":"Oracle не смог выделить непрерывный участок памяти запрошенного размера в пуле разделяемой памяти (обычно shared pool или large pool). Компонент SGA испытывает нехватку памяти.","cause":"Фрагментация shared pool из-за множества уникальных SQL-операторов без bind-переменных (полных литералов), недостаточный SHARED_POOL_SIZE, слишком много одновременных подключений, крупные пакеты PL/SQL или неверная настройка размеров ASMM/AMM.","fix":"Проверьте, к какому пулу относится сообщение об ошибке (shared pool, large pool и т. д.). Краткосрочно: ALTER SYSTEM FLUSH SHARED_POOL (осторожно: в продакшене это вынуждает повторно разбирать весь SQL, то есть вызывает временный шторм hard parse, и не устраняет фрагментацию навсегда). Постоянное решение: обеспечьте использование bind-переменных (в том числе через CURSOR_SHARING), увеличьте SGA_TARGET / SHARED_POOL_SIZE / LARGE_POOL_SIZE. Закрепите часто используемые пакеты в памяти с помощью DBMS_SHARED_POOL.KEEP. Изучите распределение памяти через V$SGASTAT.","tags":["memory","shared memory","shared pool","sga","ora-4031","bind"]},{"code":"ORA-12154","title":"TNS: не удалось разрешить указанный connect identifier","severity":"caution","meaning":"Имя службы (connect identifier / TNS-псевдоним), используемое при подключении, не удалось разрешить. Oracle Net не может сопоставить указанное имя с информацией о сервере/службе.","cause":"Псевдоним отсутствует в tnsnames.ora, переменная окружения TNS_ADMIN указывает на неверный каталог, имя псевдонима написано с ошибкой, отсутствует конфигурация именования EZConnect/LDAP или неверен порядок NAMES.DIRECTORY_PATH в sqlnet.ora.","fix":"Убедитесь, что используемый псевдоним определён в tnsnames.ora. Проверьте подключение: tnsping service_name. Убедитесь, что TNS_ADMIN указывает на правильный каталог. В качестве альтернативы попробуйте EZConnect: app_user/password@host:1521/service_name. Проверьте строку NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT) в sqlnet.ora.","tags":["tns","connect","connection","network","networking","tnsnames","service"]},{"code":"ORA-12514","title":"TNS: listener в данный момент не знает службу, запрошенную в connect descriptor","severity":"caution","meaning":"Listener работает, но не распознаёт запрошенное имя службы; то есть служба не зарегистрирована в listener.","cause":"Экземпляр базы данных открыт не полностью, служба ещё не зарегистрировалась в listener (LREG в 12c+, задержка регистрации PMON в 11g и ранее), используется неверный SERVICE_NAME или перепутаны SID и имя службы.","fix":"Просмотрите службы, известные listener: lsnrctl status и lsnrctl services. Убедитесь, что база данных в состоянии OPEN. Инициируйте регистрацию службы: ALTER SYSTEM REGISTER в SQL. Проверьте параметр LOCAL_LISTENER и правильность использования SERVICE_NAME. В подключаемой базе данных убедитесь, что PDB в состоянии OPEN.","tags":["tns","listener","service","service_name","connection","network","networking"]},{"code":"ORA-00942","title":"Таблица или представление не существует","severity":"caution","meaning":"Таблица или представление, на которые ссылается запрос, не существуют или не видны обращающемуся пользователю.","cause":"Объект действительно не существует, он находится в другой схеме, а префикс схемы не указан, у пользователя нет привилегий на объект (например, SELECT), отсутствует синоним или имя объекта написано с ошибкой.","fix":"Проверьте, существует ли объект и кто им владеет: SELECT owner, object_type FROM all_objects WHERE object_name = 'TABLE_NAME'. Если он в другой схеме, укажите полное имя schema.table или выдайте GRANT SELECT ON app_schema.my_table TO app_user. При необходимости создайте публичный/частный синоним: CREATE SYNONYM my_table FOR app_schema.my_table. Следите за регистром и идентификаторами в кавычках.","tags":["table","missing table","view","missing view","grant","privilege","synonym","alias"]},{"code":"ORA-01031","title":"Недостаточно привилегий","severity":"caution","meaning":"У пользователя нет привилегии (системной или объектной), необходимой для выполняемой операции.","cause":"Необходимая системная или объектная привилегия не выдана, привилегия предоставлена только через роль (роли не действуют внутри блоков PL/SQL с правами определившего), административная операция выполнена без SYSDBA/SYSOPER или отсутствует прямая выдача для процедур с правами определившего.","fix":"Выдайте недостающую привилегию, например: GRANT CREATE TABLE TO app_user или GRANT SELECT ON app_schema.my_table TO app_user. Помните, что для именованных объектов PL/SQL привилегии должны выдаваться напрямую, а не через роль. Для операций, требующих SYSDBA, подключайтесь с правильными учётными данными. Проверьте текущие привилегии: SELECT * FROM session_privs / user_sys_privs.","tags":["privilege","privileges","grant","permission","auth","role grant","role"]},{"code":"ORA-00257","title":"Ошибка архиватора. До освобождения разрешено только внутреннее подключение","severity":"danger","meaning":"Процесс архиватора (ARCH) не может архивировать redo-журналы; обычно заполнено место назначения архивов (FRA или каталог архивных журналов). База данных приостанавливает новые транзакции.","cause":"Заполнена Flash Recovery Area (DB_RECOVERY_FILE_DEST) или место назначения архивных журналов, закончилось место на диске, бэкапы RMAN не удаляют архивные журналы или достигнут лимит DB_RECOVERY_FILE_DEST_SIZE.","fix":"Освободите место: сделайте бэкап старых архивных журналов с помощью RMAN в место назначения вне FRA и удалите их: RMAN> BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT (запись бэкапа в заполненную FRA завершится ошибкой). При Data Guard не удаляйте архивы, ещё не переданные на standby или не применённые на нём; иначе на standby образуется разрыв (CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY). Просмотрите использование FRA: SELECT * FROM v$recovery_file_dest / v$flash_recovery_area_usage. Временно увеличьте DB_RECOVERY_FILE_DEST_SIZE или перенесите место назначения на диск большего объёма. Проверьте свободное место на уровне диска. Введите регулярную политику очистки архивов.","tags":["archive","archivelog","archiver","fra","disk","space","redo"]},{"code":"ORA-01000","title":"Превышено максимальное число открытых курсоров","severity":"caution","meaning":"Сессия открыла больше курсоров, чем допускает параметр OPEN_CURSORS. Новый курсор открыть невозможно.","cause":"Приложение не закрывает открываемые объекты Statement/ResultSet (утечка курсоров), OPEN_CURSORS задан слишком низким для нагрузки или в цикле постоянно подготавливаются новые prepared statements, которые никогда не закрываются.","fix":"Отслеживайте открытые курсоры: SELECT * FROM v$open_cursor WHERE sid = ...  и смотрите количество по сессиям через v$sesstat. Настоящее решение — закрывать каждый Statement/ResultSet в блоке finally в приложении (устранение утечки курсоров). В качестве обходного пути увеличьте OPEN_CURSORS: ALTER SYSTEM SET OPEN_CURSORS = 1000. Проверьте размер кэша операторов JDBC.","tags":["cursor","open cursor","open_cursors","leak","cursor leak","jdbc"]},{"code":"ORA-00018","title":"Превышено максимальное число сессий","severity":"danger","meaning":"База данных достигла максимального числа сессий, допускаемого параметром SESSIONS; новую сессию открыть невозможно.","cause":"SESSIONS/PROCESSES заданы ниже потребностей нагрузки, завышенная конфигурация пула подключений, утечка подключений или накопление сессий, которые никогда не закрываются.","fix":"Просмотрите текущее число сессий: SELECT COUNT(*) FROM v$session. Проверьте лимиты: SELECT * FROM v$resource_limit WHERE resource_name IN ('sessions','processes'). Увеличьте PROCESSES и тем самым косвенно SESSIONS (через SPFILE, требуется перезапуск): ALTER SYSTEM SET PROCESSES = 500 SCOPE=SPFILE. Проверьте размеры пулов подключений и утечки в приложении.","tags":["session","session limit","sessions","processes","connection","connections","limit"]},{"code":"ORA-28000","title":"Учётная запись заблокирована","severity":"caution","meaning":"Вход невозможен, поскольку учётная запись пользователя базы данных, к которой выполняется подключение, заблокирована.","cause":"Превышен лимит FAILED_LOGIN_ATTEMPTS в профиле (повторные неверные пароли) или учётная запись заблокирована DBA вручную (ALTER USER ... ACCOUNT LOCK). Блокировка из-за неудачных входов снимается автоматически по истечении PASSWORD_LOCK_TIME. Примечание: истечение PASSWORD_LIFE_TIME не блокирует учётную запись; она переходит в состояние EXPIRED / EXPIRED(GRACE) и возвращает ORA-28001/ORA-28002.","fix":"Проверьте состояние учётной записи: SELECT username, account_status, lock_date FROM dba_users WHERE username = 'APP_USER'. Разблокируйте её: ALTER USER app_user ACCOUNT UNLOCK. Чтобы не допустить повторения, найдите источник неудачных входов и при необходимости пересмотрите FAILED_LOGIN_ATTEMPTS в профиле. Если срок действия пароля истёк, назначьте новый пароль.","tags":["account","user account","locked","account locked","auth","password","profile"]},{"code":"ORA-01578","title":"Повреждён блок данных Oracle (file #, block #)","severity":"danger","meaning":"Блок данных в указанном файле данных повреждён; Oracle сообщает, что блок не прошёл проверку целостности (контрольную сумму). Это физическое повреждение.","cause":"Отказ диска/оборудования, ошибки уровня хранения, проблемы операционной системы или контроллера, частично записанные блоки после отключения питания или повреждённый бэкап/восстановление.","fix":"Подтвердите повреждение и его масштаб: RMAN> VALIDATE DATABASE / VALIDATE CHECK LOGICAL DATAFILE n; также запустите DBVERIFY (dbv) и проверьте v$database_block_corruption. Если есть бэкап, восстановите на уровне блоков: RMAN> RECOVER DATAFILE n BLOCK m; или для всех перечисленных блоков RMAN> RECOVER CORRUPTION LIST; (старая команда BLOCKRECOVER устарела начиная с 11g). Если это блок индекса, перестройте индекс. Всегда проверяйте исправность оборудования/дисков; первопричина обычно в системе хранения.","tags":["corruption","block corruption","block","corrupt block","datafile","rman","disk"]},{"code":"ORA-01722","title":"Недопустимое число","severity":"safe","meaning":"Строка символов, которую невозможно преобразовать в число, использована там, где требуется числовой контекст. Неявное или явное преобразование завершилось ошибкой.","cause":"Сравнение числового столбца с текстом (например, WHERE num_col = 'ABC'), вставка значения с буквами в числовое поле, неподходящий формат для TO_NUMBER или несоответствие десятичного/группового разделителя (запятая или точка).","fix":"Найдите выражение с неудачным преобразованием; обычно это условие WHERE или значение INSERT. Обеспечьте совместимость типов при сравнении текста и чисел и при необходимости используйте явное преобразование: TO_NUMBER(my_column) или TO_CHAR для текстовой стороны. Не допускайте ввода нечисловых данных в числовые столбцы на уровне приложения. Обратите внимание на настройку NLS_NUMERIC_CHARACTERS.","tags":["number","numeric","conversion","type conversion","to_number","datatype","data type"]},{"code":"ORA-02049","title":"Тайм-аут: распределённая транзакция ожидает блокировку","severity":"danger","meaning":"Распределённая транзакция превысила DISTRIBUTED_LOCK_TIMEOUT, ожидая блокировку в удалённой базе данных. Истёк тайм-аут транзакции.","cause":"Транзакция, выполняемая через database link, ждёт заблокированную строку на удалённой стороне; длительные транзакции в удалённой сессии, сетевая задержка или конфликтующие блокировки на обеих сторонах.","fix":"Найдите и устраните блокирующую блокировку и сессию в удалённой базе данных (v$lock, v$session). Делайте распределённые транзакции короткими и захватывайте необходимые блокировки как можно позже. При необходимости увеличьте DISTRIBUTED_LOCK_TIMEOUT под нагрузку (SPFILE, требуется перезапуск). Для распределённых транзакций в состоянии in-doubt изучите представление DBA_2PC_PENDING и при необходимости разрешите их вручную.","tags":["distributed","distributed transaction","dblink","lock","lock wait","timeout","2pc"]},{"code":"enq: TX - row lock contention","title":"Ожидание: сессия заблокирована блокировкой уровня строки, удерживаемой другой транзакцией","severity":"caution","meaning":"Сессия ждёт освобождения блокировки строки, удерживаемой другой транзакцией. Это классическое событие ожидания блокировки на уровне приложения.","cause":"Параллельные транзакции, пытающиеся обновить одну и ту же строку, транзакции, долго остающиеся без commit, широкие блокировки из-за неиндексированных внешних ключей или концентрация на последовательном ключе (горячий блок).","fix":"Найдите блокирующие и заблокированные сессии: SELECT * FROM v$session WHERE blocking_session IS NOT NULL; и сопоставьте с v$lock. Если первопричина — длительные транзакции, пересмотрите стратегию commit. Индексируйте столбцы внешних ключей. Для горячих строк пересмотрите дизайн (sequence, hash). При необходимости завершите блокирующую сессию.","tags":["wait","wait event","enq","row lock","row lock wait","tx","contention","lock"]},{"code":"log file sync","title":"Ожидание: сессия ждёт, пока LGWR сбросит redo на диск при commit","severity":"caution","meaning":"Когда сессия выполняет COMMIT, она ждёт, пока LGWR запишет (сбросит) записи redo в файлы redo-журналов. Если это ожидание велико, присутствует задержка commit.","cause":"Медленные диски redo-журналов (высокая задержка I/O), приложение, выполняющее commit слишком часто (commit на каждую строку), нехватка CPU у процесса LGWR или очень большой объём redo.","fix":"Измерьте среднее время ожидания с помощью AWR/Statspack. Перенесите файлы redo-журналов на быстрые диски с низкой задержкой (SSD). Сократите избыточную частоту commit в приложении и группируйте пакетные операции. При необходимости оцените параметры COMMIT_LOGGING/COMMIT_WAIT (commit_write) с учётом бизнес-требований; ВНИМАНИЕ: NOWAIT/BATCH снижают надёжность, и транзакции, которые выглядят зафиксированными, могут быть потеряны при сбое. Используйте их только на уровне сессии или оператора для работ, где потеря допустима; не включайте их на уровне всей системы. Изучайте задержки LGWR и системы хранения вместе.","tags":["wait","wait event","log file sync","commit","lgwr","redo","latency"]},{"code":"db file sequential read","title":"Ожидание: одноблочное физическое чтение, обычно поиск по индексу или таблице","severity":"caution","meaning":"Сессия ждёт чтения одного блока с диска. Обычно наблюдается при доступе по индексу или одноблочных чтениях по ROWID; как правило, это ожидаемое событие.","cause":"Чтение с диска блоков, отсутствующих в buffer cache, неселективное или неверное использование индексов, недостаточный buffer cache или медленный уровень хранения.","fix":"Если значение высокое, найдите SQL-операторы с наибольшим числом чтений (AWR Top SQL, v$sql). Улучшите запросы с помощью подходящих индексов и правильных путей доступа и сократите лишние nested loops. Оцените размер buffer cache (DB_CACHE_SIZE). Проверьте задержку I/O системы хранения. Это событие часто нормально; если оно аномально высокое, сосредоточьтесь на настройке SQL.","tags":["wait","wait event","db file sequential read","io","disk io","index","index access","single block"]},{"code":"db file scattered read","title":"Ожидание: многоблочное физическое чтение, обычно полное сканирование таблицы или index fast full scan","severity":"caution","meaning":"Сессия ждёт чтения нескольких блоков (multiblock) с диска. Обычно происходит при полном сканировании таблицы или index fast full scan.","cause":"Полные сканирования таблиц из-за отсутствия подходящего индекса, неверный план из-за устаревшей статистики, сканирование очень больших таблиц или выбранный путь доступа, откатывающийся к сканированию таблицы.","fix":"Определите SQL-операторы, выполняющие полные сканирования, и добавьте необходимые индексы либо обеспечьте использование существующего индекса. Обновите статистику: DBMS_STATS.GATHER_TABLE_STATS. Если крупное сканирование действительно необходимо, рассмотрите parallel query или секционирование. Проверьте DB_FILE_MULTIBLOCK_READ_COUNT и пропускную способность системы хранения.","tags":["wait","wait event","db file scattered read","full scan","full table scan","io","disk io","multiblock"]},{"code":"buffer busy waits","title":"Ожидание: конкуренция за буфер, который уже читается или изменяется другой сессией","severity":"caution","meaning":"Сессия ждёт доступа к блоку буфера, который другая сессия одновременно читает или изменяет. Это конкуренция за блок в памяти.","cause":"Концентрация на горячих блоках, конкуренция на правом краю индексов с монотонно возрастающими ключами, недостаточное число freelists/сегменты без ASSM или интенсивный параллельный DML над одним и тем же блоком.","fix":"Определите затронутый сегмент и класс блоков (v$session, v$segment_statistics, ASH). Для горячих блоков индекса рассмотрите reverse key или hash-секционированный индекс. Используйте ASSM (Automatic Segment Space Management). При высокой параллельности увеличьте INITRANS/PCTFREE. Распределите паттерн доступа приложения.","tags":["wait","wait event","buffer busy","hot block","hot blocks","contention","block contention"]},{"code":"gc buffer busy","title":"Ожидание (RAC): конкуренция за блок cache fusion, передаваемый между экземплярами","severity":"caution","meaning":"В среде RAC сессия ждёт доступа к глобальному блоку, который передаётся через cache fusion в другой экземпляр или из него либо удерживается этим экземпляром занятым.","cause":"Одни и те же горячие блоки, разделяемые узлами RAC (межэкземплярный горячий блок), задержка/узкое место interconnect, межузловая конкуренция на индексах с последовательными ключами или несбалансированное распределение нагрузки.","fix":"Найдите затронутые сегменты через v$segment_statistics и представления GV$. Проверьте исправность и задержку interconnect (выделенная быстрая сеть). Для горячих индексов используйте reverse key или hash-секционирование. Сократите межузловую конкуренцию за одни и те же блоки, разделив нагрузку между узлами по службам.","tags":["wait","wait event","rac","gc buffer busy","cache fusion","interconnect","global"]},{"code":"library cache lock","title":"Ожидание: конкуренция за дескриптор объекта library cache (сериализация parse/DDL)","severity":"caution","meaning":"Сессия ждёт блокировку объекта в library cache (SQL, PL/SQL, определение таблицы). Обычно вызвано сериализацией операций parse и DDL.","cause":"DDL (ALTER/CREATE) над интенсивно используемыми объектами, частая перекомпиляция объектов PL/SQL (инвалидация), чрезмерный hard parse, инвалидация зависимых объектов или интенсивные шторма одновременных подключений.","fix":"Найдите блокирующую сессию и объект (v$session, ASH, при необходимости диагностика library cache lock). Не выполняйте DDL над интенсивно используемыми объектами в часы пик. Сокращайте цепочки зависимостей и предотвращайте лишние перекомпиляции. Снижайте hard parse за счёт bind-переменных. Сглаживайте шторма подключений с помощью пула подключений.","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":"Вставленная или обновлённая строка нарушает ограничение UNIQUE или PRIMARY KEY; такое значение уже существует в таблице.","cause":"Повторная вставка с тем же уникальным ключом, ввод дублирующихся данных в уникальный столбец, отставание значения последовательности из-за ручных вставок или два параллельных запроса, вставляющих один и тот же ключ.","fix":"Имя ограничения и значение ключа в деталях ошибки показывают, какая строка конфликтует. Чтобы обновить существующую строку, используйте INSERT ... ON CONFLICT (my_column) DO UPDATE или DO NOTHING. Поиск дубликатов: SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. Если последовательность отстала: 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":"Нарушено ограничение FOREIGN KEY. Родительская строка, на которую ссылается вставленная/обновлённая строка, не существует, или у удаляемой родительской строки ещё есть зависимые дочерние строки.","cause":"INSERT/UPDATE со ссылкой на несуществующий родительский ключ, попытка удалить/обновить родительскую строку, на которую ещё есть ссылки, или загрузка данных в неверном порядке (сначала дочерние, затем родительские).","fix":"Сначала вставьте родительскую строку, затем дочернюю. Если при удалении нужно каскадное поведение, определите ограничение с ON DELETE CASCADE или ON DELETE SET NULL. При массовой загрузке загружайте данные в порядке родитель — потомок. Поиск ссылающихся строк: 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":"Предпринята попытка вставить NULL в столбец с ограничением NOT NULL или присвоить этому столбцу NULL.","cause":"Пропуск обязательного столбца в INSERT, явное присвоение NULL, отсутствие у столбца DEFAULT или пустое/отсутствующее значение, пришедшее из приложения.","fix":"Передайте допустимое значение для столбца или определите для него разумный DEFAULT: ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT value. Если столбец на самом деле не обязан быть обязательным, снимите ограничение: ALTER TABLE my_table ALTER COLUMN my_column DROP NOT NULL. Добавьте проверку отсутствующих данных на уровне приложения.","tags":["not null","null","constraint","constraint","required","23502","insert"]},{"code":"40P01","title":"deadlock_detected","severity":"danger","meaning":"Две или более транзакции оказались во взаимоблокировке, каждая ждёт блокировки, удерживаемые другой. PostgreSQL обнаруживает цикл и отменяет одну из транзакций.","cause":"Захват блокировок в разном порядке, перекрёстные обновления одних и тех же строк, длительные транзакции или конфликтующие блокировки, захватываемые при проверках внешних ключей.","fix":"Детали взаимоблокировки в журнале сервера показывают конфликтующие запросы. Во всех транзакциях обращайтесь к таблицам/строкам в одном и том же порядке. Делайте транзакции короткими. Перехватывайте отменённую транзакцию в приложении и повторяйте её. Для изучения блокировок соедините pg_locks с pg_stat_activity. При необходимости задайте порядок блокировок явно с помощью SELECT ... FOR UPDATE.","tags":["deadlock","deadlock","lock","lock","40P01","tx","transaction"]},{"code":"40001","title":"serialization_failure","severity":"caution","meaning":"На уровне изоляции SERIALIZABLE или REPEATABLE READ транзакцию не удалось сериализовать, поскольку она конфликтовала с параллельными изменениями, и она была отменена. Это ожидаемое поведение, защищающее согласованность данных.","cause":"Транзакции, параллельно обращающиеся к одним и тем же данным, невозможно упорядочить сериализуемым образом, конфликты записи при высокой параллельности или опасные зависимости, обнаруженные SSI (Serializable Snapshot Isolation).","fix":"Это не ошибка, а ожидаемый сигнал: повторите транзакцию как есть. Добавьте в приложение логику автоматического повтора. Чтобы сократить конфликты, укорачивайте транзакции и используйте более низкий уровень изоляции (READ COMMITTED), если более высокий действительно не нужен. Проектируйте так, чтобы снизить конкуренцию за горячие строки.","tags":["serialization","serialization","isolation","isolation","retry","retry","40001","mvcc"]},{"code":"53300","title":"too_many_connections","severity":"danger","meaning":"Сервер достиг максимального числа одновременных подключений, допускаемого max_connections; новые подключения не принимаются.","cause":"Отсутствие пула подключений или его неверный размер, утечки подключений, приложения, открывающие множество кратковременных подключений, или max_connections ниже потребностей нагрузки.","fix":"Определите источники подключений: SELECT usename, state, COUNT(*) FROM pg_stat_activity GROUP BY 1,2. Завершайте давно простаивающие клиентские подключения адресно (массовое завершение без фильтра затрагивает все приложения и пулы): 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(). Настоящее решение — пул подключений, например PgBouncer. Увеличение max_connections требует памяти; предпочитайте пулинг. Подключения, зарезервированные для суперпользователей, сохраняют возможность административного доступа.","tags":["connection","connection","max_connections","too many","53300","pool","pool","pgbouncer"]},{"code":"53100","title":"disk_full","severity":"danger","meaning":"Сервер не смог выполнить запись в файл, поскольку диск заполнен. На диске с каталогом данных, WAL или временными файлами не осталось места.","cause":"Рост объёма данных, неконтролируемое накопление WAL (архивирование или отстающий replication slot), большие временные файлы (огромные сортировки), разросшиеся файлы журналов или bloat таблиц/индексов.","fix":"Проверьте использование диска и удалите ненужные файлы. Проверьте накопление WAL: pg_stat_replication и pg_replication_slots (удалите неиспользуемые слоты; отстающие удерживают WAL). Не запускайте VACUUM FULL или REINDEX при заполненном диске: поскольку они перезаписывают таблицу, им нужно дополнительное место, равное её размеру, и это может ухудшить ситуацию; обычный VACUUM также (почти) никогда не возвращает место операционной системе. НИКОГДА не удаляйте файлы в каталоге pg_wal вручную. Сначала освободите место, устранив источники WAL/слотов, журналов и temp; пересмотрите размещение журналов и временных файлов. В качестве постоянного решения увеличьте ёмкость диска и настройте мониторинг/оповещения.","tags":["disk","space","space","full","full","wal","53100","vacuum"]},{"code":"57014","title":"query_canceled","severity":"safe","meaning":"Запрос был отменён. Обычно причина — истечение statement_timeout или отмена пользователем либо клиентом (pg_cancel_backend, отмена драйвером).","cause":"Превышен statement_timeout, отмена через pg_cancel_backend или запрос на отмену, отправленный приложением/клиентом. Похожие ситуации имеют другие коды: истечение lock_timeout возвращает 55P03, idle_in_transaction_session_timeout — 25P03 (FATAL), а отмена из-за конфликта репликации на hot standby — 40001 (в некоторых случаях 40P01).","fix":"Если запрос действительно выполняется долго, оптимизируйте его (EXPLAIN ANALYZE, индексы, план). Если тайм-аут слишком мал, настройте statement_timeout для соответствующей сессии/роли. Намеренные отмены не требуют действий. Для отмен, вызванных блокировками и конфликтами на standby, см. записи 55P03 и 40001.","tags":["cancel","cancel","timeout","timeout","statement_timeout","57014","standby"]},{"code":"42P01","title":"undefined_table","severity":"caution","meaning":"Таблица (или представление), на которую ссылается запрос, не существует или не найдена в search_path.","cause":"Таблица действительно не существует, она находится в другой схеме, не входящей в search_path, опечатка/ошибка регистра в имени таблицы, несовпадение регистра с идентификаторами в кавычках или объект ещё не создан либо был удалён.","fix":"Убедитесь, что таблица существует, и проверьте её схему: SELECT schemaname, tablename FROM pg_tables WHERE tablename = 'my_table'. Если она в другой схеме, укажите полное имя schema.table или задайте search_path: SET search_path TO app_schema, public. Помните, что идентификаторы без кавычек приводятся к нижнему регистру; если при создании использовались кавычки, обращайтесь к объекту так же.","tags":["table","table","undefined","42P01","search_path","schema","schema"]},{"code":"42703","title":"undefined_column","severity":"caution","meaning":"Столбец, на который ссылается запрос, не существует в таблице или в текущей области видимости.","cause":"Опечатка в имени столбца, столбец удалён/переименован, ссылка на неверную таблицу, несовпадение регистра идентификатора в кавычках или неоднозначная/неверная квалификация в запросе к нескольким таблицам.","fix":"Выведите список столбцов: \\d my_table или SELECT column_name FROM information_schema.columns WHERE table_name = 'my_table'. Исправьте имя и таблицу, а в JOIN указывайте столбцы как table.column. Если при создании использовались кавычки, обращайтесь к столбцу с тем же регистром.","tags":["column","column","undefined","42703","typo","typo","schema"]},{"code":"42501","title":"insufficient_privilege","severity":"caution","meaning":"У роли нет привилегии, необходимой для выполняемой операции.","cause":"Не выдан необходимый GRANT на таблицу/схему/базу данных, отсутствует USAGE на схему, объект принадлежит другой роли или доступ блокируют политики защиты на уровне строк (RLS).","fix":"Выдайте недостающую привилегию: GRANT SELECT, INSERT ON my_table TO app_role; для схемы GRANT USAGE ON SCHEMA app_schema TO app_role. Для массовой выдачи используйте GRANT ... ON ALL TABLES IN SCHEMA, а для будущих объектов — ALTER DEFAULT PRIVILEGES. Просмотр текущих привилегий: \\dp my_table. Если RLS активна, проверьте соответствующие политики.","tags":["privilege","privilege","permission","grant","42501","permission","rls"]},{"code":"28P01","title":"invalid_password","severity":"caution","meaning":"Пароль, указанный при аутентификации, неверен; вход для роли отклонён.","cause":"Неверный пароль, изменённый пароль, устаревшие учётные данные в строке подключения приложения или несоответствие методу аутентификации, ожидаемому в pg_hba.conf (scram-sha-256, md5).","fix":"Проверьте пароль и при необходимости сбросьте его: ALTER ROLE app_role WITH PASSWORD 'new_password'. Убедитесь, что метод в pg_hba.conf совместим с клиентом (у старых клиентов возможны проблемы с scram-sha-256). После изменений перечитайте конфигурацию с помощью SELECT pg_reload_conf().","tags":["auth","identity","password","password","28P01","scram","md5","hba"]},{"code":"08006","title":"connection_failure","severity":"danger","meaning":"Не удалось установить соединение между клиентом и сервером, или оно неожиданно разорвалось.","cause":"Сервер не запущен или перезапускается, проблемы сети/межсетевого экрана, неверный хост/порт, настройка listen_addresses, блокирующая доступ, простаивающее подключение, разорванное сетью, или перегруженный сервер.","fix":"Убедитесь, что сервер работает и прослушивает правильный хост/порт (pg_isready host port). Проверьте listen_addresses в postgresql.conf и правила доступа в pg_hba.conf. Проверьте настройки сети/межсетевого экрана и DNS. Проверьте настройки TCP keepalive и тайм-аутов в пуле подключений. Поищите в журнале сервера следы сбоя/перезапуска.","tags":["connection","connection","network","network","08006","failure","disconnect"]},{"code":"55P03","title":"lock_not_available","severity":"caution","meaning":"Запрошенную блокировку не удалось получить немедленно. Обычно это происходит, когда блокировка запрашивается с NOWAIT, пока её удерживает кто-то другой, или когда истекает lock_timeout.","cause":"Строка/таблица заблокирована при использовании SELECT ... FOR UPDATE NOWAIT или LOCK TABLE ... NOWAIT либо истёк lock_timeout. Чаще всего блокировку удерживает длительная транзакция.","fix":"Найдите блокирующую сессию: соедините pg_locks и pg_stat_activity (связь ожидающий/блокирующий). Завершите длительную транзакцию или при необходимости прервите её с помощью pg_terminate_backend(pid). Вместо NOWAIT ждите с разумным lock_timeout и добавьте логику повтора. Укорачивайте транзакции, чтобы сократить время удержания блокировок.","tags":["lock","lock","nowait","lock_timeout","55P03","not available","busy"]},{"code":"54000","title":"program_limit_exceeded","severity":"caution","meaning":"Превышен предел реализации/формата PostgreSQL; например, максимальный размер строки или поля, размер строки индекса или ограничение на число параметров/столбцов в запросе.","cause":"Попытка сохранить очень большое значение (например, поле больше 1 GB), индексируемое значение, превышающее предел страницы B-tree (около 2704 байт), или использование слишком большого числа столбцов либо параметров запроса.","fix":"Определите превышенный предел (обычно он указан в сообщении). Пересмотрите или разделите структуру для очень больших текстов/данных. Индексируйте длинные текстовые столбцы hash-индексом или индексом по выражению/частичным индексом вместо B-tree. Сократите число параметров/столбцов в запросе или разделите пакет.","tags":["limit","limit","program_limit","54000","index size","size","row size"]},{"code":"22001","title":"string_data_right_truncation","severity":"caution","meaning":"Значение было бы усечено, поскольку длина текста превышает заданную длину целевого столбца (например, VARCHAR(n)); PostgreSQL отклоняет его и выдаёт ошибку.","cause":"Вставка или обновление текста длиннее n в столбце VARCHAR(n)/CHAR(n), более длинные, чем ожидалось, данные из приложения или слишком узкая для потребностей длина столбца.","fix":"Сократите значение так, чтобы оно помещалось в столбец, или увеличьте длину столбца: ALTER TABLE my_table ALTER COLUMN my_column TYPE VARCHAR(500). Если нужен текст без ограничения длины, используйте тип TEXT. Проверяйте длину в приложении. Если требуется намеренное усечение, применяйте LEFT(value, n) явно.","tags":["truncation","truncation","varchar","string","text","22001","length","length"]},{"code":"25P02","title":"in_failed_sql_transaction","severity":"caution","meaning":"Ранее в транзакции уже произошла ошибка; дальнейшие команды в ней выполнить нельзя. Транзакция находится в прерванном состоянии, и принимается только ROLLBACK.","cause":"Предыдущий оператор в блоке транзакции завершился ошибкой (например, нарушение ограничения), но в той же транзакции продолжают отправляться новые команды. Приложение не перехватывает ошибку и продолжает транзакцию.","fix":"Откатите транзакцию: ROLLBACK, затем начните её заново. Если нужно изолировать ошибку блока и продолжить, используйте SAVEPOINT и возвращайтесь к этой точке с помощью ROLLBACK TO SAVEPOINT. В приложении перехватывайте ошибку каждого оператора и корректно управляйте транзакцией (делайте решение о commit/rollback явным).","tags":["transaction","transaction","aborted","aborted","25P02","rollback","savepoint"]},{"code":"53200","title":"out_of_memory","severity":"danger","meaning":"Сервер не смог выделить память, необходимую для операции. Обслуживающий процесс (backend) или весь сервер испытывает нехватку памяти.","cause":"Слишком большие work_mem/maintenance_work_mem, умноженные на высокую параллельность, слишком много операций hash/sort в одном запросе, чрезмерное число подключений, утечка памяти или давление со стороны OOM killer операционной системы.","fix":"Проверьте контекст памяти в журнале сервера и следы OOM killer. Задавайте work_mem разумно с учётом числа запросов и параллельности (она выделяется отдельно для каждой сортировки/хеширования). Ограничьте число подключений с помощью пулинга. Проверьте настройки памяти/overcommit операционной системы. Оптимизируйте тяжёлые запросы, чтобы снизить их потребность в памяти.","tags":["memory","memory","out of memory","53200","work_mem","oom"]}]},"errors/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1045","title":"Доступ запрещён для пользователя (using password: YES/NO)","severity":"caution","meaning":"Аутентификация не прошла из-за имени пользователя, пароля или хоста, с которого выполняется подключение; вход отклонён.","cause":"Неверный пароль, у пользователя нет привилегий с хоста, с которого он подключается (user@host), учётная запись не существует, несоответствие плагина аутентификации (caching_sha2_password и mysql_native_password) или устаревшие учётные данные в строке подключения.","fix":"Проверьте соответствие пользователя и хоста: SELECT user, host, plugin FROM mysql.user. Выдайте привилегии для правильного хоста (например, 'app_user'@'%'). Сбросьте пароль: ALTER USER 'app_user'@'host' IDENTIFIED BY 'new_password'; (ALTER USER/GRANT действуют немедленно; FLUSH PRIVILEGES нужен, только если таблицы привилегий изменялись напрямую через DML). Если вы видите 'using password: NO', клиент вообще не отправляет пароль. При несоответствии плагинов проверьте плагин аутентификации: в MySQL 8.4 плагин mysql_native_password по умолчанию отключён (загружается только с --mysql-native-password=ON); старые учётные записи с этим плагином и старые клиенты не могут подключиться. Переведите учётную запись с помощью ALTER USER ... IDENTIFIED WITH caching_sha2_password BY '...'; и обновите клиентский драйвер.","tags":["auth","authentication","access denied","access","password","password","1045","privilege"]},{"code":"1062","title":"Дублирующаяся запись для ключа (Duplicate entry)","severity":"caution","meaning":"Вставляемая или обновляемая строка нарушает UNIQUE-индекс или PRIMARY KEY; такое значение ключа уже существует.","cause":"Повторный INSERT с тем же уникальным ключом, ввод дублирующихся данных, отставший счётчик AUTO_INCREMENT или существующие дубликаты при добавлении уникального индекса к существующей таблице.","fix":"Сообщение об ошибке показывает конфликтующее значение ключа и имя индекса. Чтобы обновить существующую строку, рассмотрите INSERT ... ON DUPLICATE KEY UPDATE или INSERT IGNORE / REPLACE. Поиск дубликатов: SELECT my_column, COUNT(*) FROM my_table GROUP BY my_column HAVING COUNT(*) > 1. Если не удаётся добавить уникальный индекс, сначала устраните дубликаты.","tags":["duplicate","duplicate entry","unique","unique key","1062","primary key","insert"]},{"code":"1213","title":"Обнаружена взаимоблокировка при попытке получить блокировку; попробуйте перезапустить транзакцию","severity":"danger","meaning":"InnoDB обнаружил взаимоблокировку, при которой две или более транзакции ждут блокировки друг друга, и разорвал цикл, откатив одну из транзакций.","cause":"Захват блокировок строк в разном порядке, перекрёстные обновления одних и тех же строк, блокировка широких диапазонов строк (gap locks) из-за отсутствующих индексов или конфликты при проверках внешних ключей.","fix":"Просмотрите сведения о последней взаимоблокировке: SHOW ENGINE INNODB STATUS (раздел LATEST DETECTED DEADLOCK). Во всех транзакциях обращайтесь к строкам/таблицам в одном и том же порядке и делайте транзакции короткими. Сузьте блокируемый диапазон, добавив подходящие индексы. Перехватывайте откатанную транзакцию в приложении и повторяйте её (логика повтора).","tags":["deadlock","deadlock detected","lock","row lock","innodb","1213","tx","transaction"]},{"code":"1205","title":"Превышен тайм-аут ожидания блокировки; попробуйте перезапустить транзакцию","severity":"danger","meaning":"Транзакция ждала необходимую блокировку строки в течение innodb_lock_wait_timeout секунд, не смогла её получить и завершилась по тайм-ауту.","cause":"Другая транзакция долго удерживает блокировку без commit или rollback; простаивающие открытые транзакции, длительные массовые обновления или горячие строки с высокой конкуренцией.","fix":"Найдите блокирующую транзакцию: соедините performance_schema.data_lock_waits с data_locks (в старых версиях information_schema.innodb_lock_waits/innodb_trx). Зафиксируйте/откатите блокирующую транзакцию или при необходимости завершите её через KILL <trx_mysql_thread_id>. Делайте транзакции короткими и не оставляйте их простаивать (autocommit/логика приложения). При необходимости настройте innodb_lock_wait_timeout под нагрузку.","tags":["lock","lock wait","timeout","timed out","innodb","1205","wait","waiting"]},{"code":"1040","title":"Слишком много подключений (Too many connections)","severity":"danger","meaning":"Сервер достиг максимального числа одновременных подключений, допускаемого max_connections; новые подключения отклоняются.","cause":"Неверно настроенный размер пула подключений, утечки подключений, множество кратковременных подключений, подключения, долго находящиеся в состоянии Sleep, или слишком низкое для нагрузки значение max_connections.","fix":"Проверьте подключения: SHOW PROCESSLIST или SELECT * FROM performance_schema.processlist. Закройте лишние спящие подключения с помощью KILL. Увеличьте max_connections: SET GLOBAL max_connections = 500 (в my.cnf для сохранения). Корневое решение — использовать пул подключений и закрывать простаивающие подключения через wait_timeout. Для SUPER/CONNECTION_ADMIN зарезервировано одно дополнительное аварийное административное подключение.","tags":["connection","connections","too many","max_connections","1040","pool","connection pool"]},{"code":"1064","title":"Ошибка в синтаксисе SQL","severity":"safe","meaning":"В SQL-операторе синтаксическая ошибка; сервер не смог его разобрать. Сообщение показывает текст рядом с ошибкой.","cause":"Опечатка, отсутствующая/лишняя запятая или скобка, зарезервированное слово без кавычек в качестве имени столбца/таблицы, синтаксис, не поддерживаемый версией MySQL, или ошибки кавычек/экранирования.","fix":"Часть сообщения 'near ...' показывает, где начинается ошибка; смотрите непосредственно перед ней. Заключайте зарезервированные слова в обратные кавычки: `order`, `group`. Сбалансируйте скобки и запятые. Убедитесь, что используете синтаксис, поддерживаемый вашей версией. Если вы генерируете динамический SQL, логируйте и изучайте полный итоговый запрос.","tags":["syntax","syntax error","1064","parse","parsing","reserved word","reserved keyword"]},{"code":"1146","title":"Таблица не существует","severity":"caution","meaning":"Таблица, на которую ссылается запрос, не существует или не найдена в текущей базе данных.","cause":"Таблица действительно не существует, выбрана неверная база данных, опечатка в имени таблицы, чувствительность имён таблиц к регистру в Linux (lower_case_table_names) или отсутствующий/повреждённый файл таблицы InnoDB (.ibd) (в 5.7 и ранее также файл определения .frm; в 8.0 определения хранятся в словаре данных).","fix":"Проверьте таблицу: SHOW TABLES; и выберите правильную базу данных: USE mydb. Учитывайте чувствительность к регистру (особенно в Linux). Проверьте имя таблицы и схему через SELECT ... FROM information_schema.tables. При проблеме с файлом InnoDB может потребоваться восстановление/ремонт.","tags":["table","table missing","doesn't exist","not found","1146","schema","database"]},{"code":"1054","title":"Неизвестный столбец в списке полей","severity":"caution","meaning":"Столбец, на который ссылается запрос, не существует в таблице или в текущей области видимости.","cause":"Опечатка в имени столбца, столбец удалён/переименован, ссылка на неверную таблицу, неоднозначная/неверная квалификация в JOIN или строковое значение, записанное без кавычек и воспринятое как столбец (WHERE name = value вместо WHERE name = 'value').","fix":"Выведите список столбцов: SHOW COLUMNS FROM my_table или DESCRIBE my_table. Исправьте имя столбца и в JOIN указывайте его как my_table.my_column. Заключайте строковые литералы в одинарные кавычки; обратные кавычки — только для идентификаторов. Проверить можно через information_schema.columns.","tags":["column","column name","unknown","unknown column","1054","typo","misspelling"]},{"code":"2002","title":"Не удаётся подключиться к локальному серверу MySQL через сокет","severity":"danger","meaning":"Клиент не смог подключиться к локальному серверу MySQL через Unix-сокет. Обычно сервер не запущен или путь к сокету неверен.","cause":"Служба MySQL/MariaDB не запущена или аварийно завершилась, путь к файлу сокета (например, /var/run/mysqld/mysqld.sock) у клиента и сервера различается, проблемы с правами доступа или сервер не запустился.","fix":"Проверьте состояние службы и запустите её (systemctl status mysql / start mysql). Проверьте правильный путь к сокету: SHOW VARIABLES LIKE 'socket' и настройку socket в my.cnf. Если путь различается, укажите его явно через --socket или используйте TCP: mysql -h 127.0.0.1 -P 3306. Проверьте журналы сервера на ошибки запуска/сбоев.","tags":["connection","connect","socket","unix socket","2002","server","local","localhost"]},{"code":"2003","title":"Не удаётся подключиться к серверу MySQL (TCP/IP)","severity":"danger","meaning":"Клиент не смог подключиться к серверу MySQL по TCP/IP на указанном хосте и порту.","cause":"Сервер не запущен, неверный хост/порт, межсетевой экран блокирует порт 3306, сервер привязан только к localhost (bind-address = 127.0.0.1) или включён skip-networking.","fix":"Убедитесь, что сервер работает и прослушивает правильный порт. Для удалённого доступа задайте bind-address = 0.0.0.0 (или подходящий IP) в my.cnf и убедитесь, что skip-networking выключен. Откройте порт 3306 в межсетевом экране. Проверьте хост/порт: mysql -h host -P 3306 -u app_user -p. Убедитесь, что у пользователя есть привилегии с удалённого хоста.","tags":["connection","connect","tcp","port","2003","network","firewall","bind-address"]},{"code":"2006","title":"MySQL server has gone away (соединение с сервером потеряно)","severity":"caution","meaning":"Соединение между клиентом и сервером было потеряно во время выполнения команды. Сервер мог закрыть соединение или отклонить пакет.","cause":"Простаивающее подключение закрыто из-за истечения wait_timeout/interactive_timeout, запрос/данные больше max_allowed_packet, перезапуск или сбой сервера либо обрыв сети.","fix":"При проблемах с простаивающими подключениями используйте проверку (validation) в пуле подключений и пересмотрите wait_timeout. Если вы отправляете большие данные, увеличьте max_allowed_packet (например, SET GLOBAL max_allowed_packet = 64*1024*1024, в my.cnf для сохранения). Проверьте журналы сервера на перезапуски/сбои и следы OOM. Добавьте в приложение логику переподключения при разрыве соединений.","tags":["connection","connection lost","gone away","disconnect","2006","timeout","max_allowed_packet"]},{"code":"1114","title":"Таблица заполнена","severity":"danger","meaning":"В таблицу больше нельзя записать данные, поскольку достигнут предел хранилища или исчерпано доступное место.","cause":"Диск заполнен, достигнут предел max_heap_table_size для таблиц MEMORY, превышен tmp_table_size для временных таблиц, заполнилось табличное пространство фиксированного размера, заданное innodb_data_file_path, или ограничения размера файловой системы/конкретной таблицы.","fix":"Сначала проверьте и освободите место на диске. Для таблиц MEMORY/временных таблиц увеличьте max_heap_table_size и tmp_table_size. Для InnoDB убедитесь, что innodb_data_file_path задан с autoextend, или добавьте место. Оптимизируйте запросы, сбрасывающие большие временные результаты на диск. Для постоянного решения увеличьте ёмкость хранилища.","tags":["table full","table is full","disk","space","1114","space","memory","innodb"]},{"code":"1215","title":"Не удаётся добавить ограничение внешнего ключа","severity":"caution","meaning":"Определяемое ограничение внешнего ключа не может быть создано; MySQL не может добавить ограничение.","cause":"Несоответствие типа данных/длины/знаковости (signed-unsigned) или набора символов/сортировки между дочерним и ссылочным столбцами, отсутствие подходящего индекса/PRIMARY KEY на ссылочном столбце, таблицы с разными механизмами хранения (одна из них MyISAM) или ссылочная таблица ещё не существует.","fix":"Убедитесь, что типы столбцов с обеих сторон В ТОЧНОСТИ совпадают (например, оба INT UNSIGNED). Убедитесь, что ссылочный столбец обеспечен индексом (обычно PRIMARY KEY или UNIQUE). Убедитесь, что обе таблицы — InnoDB. Для строковых ключей используйте одинаковые charset/collation. Подробности: SHOW ENGINE INNODB STATUS (LATEST FOREIGN KEY ERROR).","tags":["foreign key","foreign key constraint","fk","1215","constraint","constraint error","innodb","type"]},{"code":"1451","title":"Не удаётся удалить или обновить родительскую строку: нарушено ограничение внешнего ключа","severity":"caution","meaning":"Вы попытались удалить родительскую строку или изменить её ключ, но ограничение внешнего ключа этого не допускает, поскольку существуют зависимые дочерние строки.","cause":"У родительской строки, на которую есть ссылки, ещё есть дочерние строки, а ограничение использует RESTRICT/NO ACTION для ON DELETE/UPDATE.","fix":"Сначала удалите зависимые дочерние строки или перенесите их к другому родителю, затем удалите родительскую строку. Если нужно автоматическое поведение, переопределите ограничение с ON DELETE CASCADE или ON DELETE SET NULL. Поиск зависимых строк: SELECT * FROM child_table WHERE fk_column = value.","tags":["foreign key","foreign key constraint","fk","1451","parent","parent row","delete","reference"]},{"code":"1452","title":"Не удаётся добавить или обновить дочернюю строку: нарушено ограничение внешнего ключа","severity":"caution","meaning":"Вы вставляете или обновляете дочернюю строку, но значение родительского ключа, на которое она указывает, отсутствует в ссылочной таблице; ограничение внешнего ключа её отклоняет.","cause":"INSERT/UPDATE со ссылкой на несуществующий родительский ключ, загрузка данных в неверном порядке (сначала дочерние) или удалённая родительская строка.","fix":"Сначала вставьте родительскую строку, затем дочернюю. Убедитесь, что ссылочное значение существует: SELECT * FROM parent_table WHERE key_column = value. Соблюдайте порядок родитель — потомок при массовой загрузке. Используйте SET FOREIGN_KEY_CHECKS=0 только в контролируемых операциях обслуживания и по завершении возвращайте значение 1.","tags":["foreign key","foreign key constraint","fk","1452","child","child row","reference","insert"]},{"code":"1366","title":"Некорректное строковое/целое значение для столбца","severity":"caution","meaning":"В столбец вставлено значение, несовместимое с его типом или набором символов. Обычно причина — несоответствие кодировки символов или типа.","cause":"Запись 4-байтового эмодзи/символа (utf8mb4) в столбец latin1, ввод нечислового текста в числовой столбец, неверная кодировка подключения или отправка значения другого типа, чем ожидается.","fix":"При проблемах с символами преобразуйте столбец/таблицу в utf8mb4: ALTER TABLE my_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci и задайте кодировку подключения utf8mb4. Для числовых полей проверяйте типы в приложении. Помните, что сервер работает в строгом режиме SQL (STRICT); первопричина — тип/кодировка данных.","tags":["charset","character","incorrect value","1366","utf8mb4","encoding","type","collation"]},{"code":"1044","title":"Доступ пользователя к базе данных запрещён","severity":"caution","meaning":"Хотя пользователь прошёл аутентификацию, у него нет привилегий на работу с указанной базой данных.","cause":"Нет GRANT на базу данных для пользователя, неверное имя базы данных, привилегия задана для другого хоста (user@host) или база данных не существует.","fix":"Просмотрите привилегии пользователя: SHOW GRANTS FOR 'app_user'@'host'. Выдайте необходимую привилегию: GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'host'; (GRANT действует немедленно, FLUSH PRIVILEGES не нужен). Проверьте правильность соответствия хоста и имени базы данных. Следуя принципу наименьших привилегий, выдавайте только необходимые права.","tags":["access denied","access","database","database access","1044","grant","privilege","permission"]},{"code":"1264","title":"Значение вне допустимого диапазона для столбца","severity":"caution","meaning":"В числовой столбец или столбец даты вставлено значение вне допустимого для типа диапазона. Эта ошибка возникает в строгом режиме SQL.","cause":"Превышение предела типов, таких как TINYINT/INT (например, 200 в TINYINT или отрицательное значение в столбец UNSIGNED), переполнение даты/числа или отправка значения больше ожидаемого.","fix":"Расширьте тип столбца по необходимости (например, INT вместо TINYINT, BIGINT вместо INT) или исправьте знаковость (SIGNED/UNSIGNED). Проверяйте диапазоны значений в приложении. Даже если вам нужно молчаливое обрезание (не рекомендуется), приводите данные в соответствие с типом, а не убирайте STRICT из 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":"Транзакция (Process ID) оказалась во взаимоблокировке с другим процессом за ресурсы блокировки и выбрана жертвой взаимоблокировки","severity":"danger","meaning":"Две или более транзакции ждут блокировки, удерживаемые друг другом (взаимоблокировка). Чтобы разорвать цикл, SQL Server выбирает одну транзакцию жертвой и откатывает её.","cause":"Типичные причины — транзакции, блокирующие таблицы/строки в разном порядке, длительные транзакции, широкие сканирования из-за отсутствующих индексов, эскалация блокировок и высокая параллельность.","fix":"Перехватывайте ошибку 1205 в приложении и автоматически повторяйте транзакцию. В каждой транзакции обращайтесь к объектам в одном и том же порядке и делайте транзакции короткими. Изучите граф взаимоблокировки: для подробностей используйте Extended Events (сессия system_health) или trace flag 1222. Добавьте подходящие индексы, чтобы сократить сканирование и блокировки. При необходимости оцените уровень изоляции READ COMMITTED SNAPSHOT (RCSI).","tags":["deadlock","lock","locking","victim","rollback","1205","retry"]},{"code":"2627","title":"Нарушение ограничения PRIMARY KEY / UNIQUE KEY. Невозможно вставить дублирующийся ключ в объект","severity":"caution","meaning":"Вы попытались вставить или обновить значение, которое уже существует в столбце (столбцах), защищённых ограничением PRIMARY KEY или UNIQUE. Дублирующиеся ключи не допускаются.","cause":"Повторная вставка того же уникального значения, UPDATE, конфликтующий с существующим значением, отсутствие проверки уникальности или два параллельных запроса, создающих одну и ту же строку.","fix":"Перед вставкой проверьте, существует ли строка, или используйте MERGE / IF NOT EXISTS. Для идемпотентных вставок применяйте шаблон «INSERT ... WHERE NOT EXISTS». Перехватывайте ошибку и возвращайте осмысленное сообщение. Если дублирующиеся данные действительно нужны, пересмотрите ограничение. Используйте IDENTITY или SEQUENCE, чтобы избежать коллизий при генерации ключей.","tags":["unique","primary key","uniqueness","duplicate","constraint","constraint violation","2627"]},{"code":"2601","title":"Невозможно вставить строку с дублирующимся ключом в объект с уникальным индексом. Дублирующееся значение ключа: ...","severity":"caution","meaning":"UNIQUE INDEX отклонил строку из-за дублирующегося значения ключа. 2627 возникает для ограничений, 2601 — для уникальных индексов.","cause":"Повторная запись существующего значения в столбец (столбцы), защищённые UNIQUE INDEX, параллельные вставки или коллизия строк, охватываемых фильтруемым уникальным индексом.","fix":"Удалите или измените дублирующееся значение. Если дубликаты необходимо допускать, рассмотрите параметр IGNORE_DUP_KEY = ON (дубликаты молча пропускаются). Добавьте проверку NOT EXISTS перед вставкой. Если есть фильтруемый индекс, проверьте его предложение WHERE и коллизию.","tags":["unique index","duplicate key","unique idx","duplicate","2601","index"]},{"code":"547","title":"Оператор INSERT/UPDATE/DELETE конфликтует с ограничением FOREIGN KEY / CHECK","severity":"caution","meaning":"Оператор нарушил ограничение FOREIGN KEY или CHECK. Он либо сослался на несуществующую родительскую строку, либо записал значение, нарушающее правило CHECK, либо попытался удалить родительскую строку, на которую ещё есть ссылки.","cause":"FK: родительская строка, на которую ссылается вставленная дочерняя строка, не существует. DELETE/UPDATE: от удаляемой/изменяемой родительской строки зависят дочерние строки. CHECK: значение столбца не удовлетворяет заданному условию.","fix":"Сначала вставьте родительскую строку, затем дочернюю. При удалении сначала удалите зависимые строки или, где уместно, определите ON DELETE CASCADE. При нарушении CHECK приведите значение в соответствие с правилом. Определите нарушенное ограничение по его имени в тексте ошибки.","tags":["foreign key","fk","check","constraint","constraint violation","referential","547"]},{"code":"18456","title":"Ошибка входа для пользователя (Login failed)","severity":"danger","meaning":"Попытка входа отклонена при аутентификации. Возможно, неверны имя пользователя/пароль, учётная запись отключена или у пользователя нет доступа к базе данных.","cause":"Неверный пароль, несуществующий логин, отключённая/заблокированная учётная запись, использование SQL-логина при включённой только аутентификации Windows или отсутствие доступа к базе данных по умолчанию. Код State в тексте ошибки указывает на реальную причину (State 5: логин не существует, State 7: логин отключён и пароль тоже неверен, State 8: неверный пароль (чаще всего), State 18: пароль необходимо сменить, State 38: указанную базу данных невозможно открыть, State 58: попытка входа с SQL-логином, когда сервер работает в режиме только аутентификации Windows).","fix":"Определите первопричину по значению State в журнале ошибок SQL Server (по соображениям безопасности подробности клиенту не передаются). Проверьте имя пользователя/пароль. Убедитесь, что сервер работает в режиме 'SQL Server and Windows Authentication Mode' (смешанный режим); если вы его изменили, перезапустите службу. Проверьте сопоставление пользователя для логина и права доступа в целевой базе данных.","tags":["login","session","authentication","authentication failed","password","18456","state"]},{"code":"8152","title":"Строковые или двоичные данные были бы усечены","severity":"caution","meaning":"Вы попытались записать данные длиннее заданной длины столбца. SQL Server останавливает оператор, чтобы предотвратить потерю данных (2628 — более новая версия той же ошибки, сообщающая столбец/значение).","cause":"Например, запись 15 символов в столбец VARCHAR(10), целевой столбец уже исходного, несоответствие типа данных/длины или недостаточный размер поля при ETL/переносах.","fix":"Расширьте столбец под данные (ALTER TABLE ... ALTER COLUMN) или сократите данные. В SQL Server 2019+ используйте ошибку 2628, показывающую переполненные таблицу/столбец/значение; в старых версиях проверяйте столбцы по одному с помощью LEN(), чтобы найти проблемную строку. При переносах выравнивайте длины в схемах источника и приёмника.","tags":["truncation","truncate","overflow","varchar","length","column length","8152","2628"]},{"code":"2628","title":"Строковые или двоичные данные были бы усечены в таблице, столбце ...; усекаемое значение","severity":"caution","meaning":"Улучшенная версия 8152 (SQL Server 2019 и новее). Явно сообщает, какие таблица, столбец и значение были бы усечены.","cause":"Запись текстового/двоичного значения длиннее заданной длины целевого столбца. Обычно это несоответствие схемы или входные данные неожиданной длины.","fix":"Посмотрите имя таблицы и столбца в тексте ошибки; либо расширьте столбец, либо сократите исходное значение. В SQL Server 2019 ошибка 2628 по умолчанию выдаётся только при уровне совместимости 150+; на более низких уровнях включите её с помощью ALTER DATABASE SCOPED CONFIGURATION SET VERBOSE_TRUNCATION_WARNINGS = ON или trace flag 460. Начиная с SQL Server 2016 SP2 CU6 и 2017 CU12 trace flag 460 даёт такое же подробное сообщение. Выравнивайте длины полей с приёмником на уровне переноса.","tags":["truncation","truncate","overflow","column","column name","length","2628","460"]},{"code":"1222","title":"Превышен тайм-аут запроса блокировки","severity":"caution","meaning":"Истёк тайм-аут запроса во время ожидания получения блокировки. Запрос отменён, поскольку другая транзакция слишком долго удерживала необходимый ресурс заблокированным.","cause":"Превышение времени ожидания, заданного через SET LOCK_TIMEOUT, длительные блокирующие транзакции, открытые незафиксированные транзакции, длинные сканирования и конфликты блокировок.","fix":"Найдите блокирующего: изучите sys.dm_exec_requests, sys.dm_tran_locks и столбец blocking_session_id или используйте sp_who2 / sp_whoisactive. Укорачивайте длительные транзакции и фиксируйте их. При необходимости настройте LOCK_TIMEOUT. В системах с преобладанием чтения сокращайте конфликты читателей и писателей с помощью RCSI/snapshot isolation.","tags":["lock timeout","lock wait timeout","blocking","blocked session","1222","lock_timeout"]},{"code":"9002","title":"Журнал транзакций базы данных заполнен по причине ...","severity":"danger","meaning":"Файл журнала транзакций базы данных заполнен, и новые транзакции не могут быть зарегистрированы. База данных отклоняет запись и может вести себя так, будто она только для чтения.","cause":"Отсутствие бэкапов журнала в модели восстановления FULL, долго открытая транзакция, отставание репликации/AlwaysOn или невозможность роста журнала (autogrow отключён или диск заполнен). Причину показывает log_reuse_wait_desc.","fix":"Проверьте log_reuse_wait_desc в sys.databases. Если это LOG_BACKUP в модели FULL, сделайте бэкап журнала (BACKUP LOG). Если это ACTIVE_TRANSACTION, найдите открытую транзакцию и зафиксируйте/откатите её (DBCC OPENTRAN). Освободите место на диске или исправьте настройку autogrow файла журнала. Если журнал действительно разросся, после бэкапа сожмите его с помощью DBCC SHRINKFILE. Если цепочка журналов не нужна, пересмотрите модель восстановления.","tags":["transaction log","log is full","log full","9002","backup log","recovery model"]},{"code":"1105","title":"Не удалось выделить место для объекта в базе данных, поскольку файловая группа заполнена","severity":"danger","meaning":"Файловая группа, содержащая файл данных, заполнена, и место для новых данных выделить невозможно. Вставки/обновления завершаются ошибкой.","cause":"Файл данных не может расти (autogrowth отключён или достигнут MAXSIZE), нет свободного места на диске, исчерпана файловая группа PRIMARY или ограничение размера файла.","fix":"Освободите место на диске или добавьте новый диск/файл. Включите autogrowth для файла данных или увеличьте его (ALTER DATABASE ... MODIFY FILE). Добавьте новый файл в файловую группу. Архивируйте и удалите ненужные/старые данные. Для tempdb проверьте файлы tempdb и их диск.","tags":["filegroup full","file full","allocate space","disk","autogrow","1105"]},{"code":"208","title":"Недопустимое имя объекта","severity":"safe","meaning":"Таблица, представление или объект, на которые ссылается запрос, не найдены. SQL Server не смог разрешить указанное имя.","cause":"Опечатка в имени объекта, неверный контекст базы данных/схемы (схема, отличная от dbo), объект действительно не существует или ещё не создан либо отсутствует двухчастное имя (schema.table).","fix":"Правильно укажите имя объекта и схему; используйте полное имя (например, dbo.Customers). Убедитесь, что вы подключены к нужной базе данных (USE). Проверьте, существует ли объект: SELECT * FROM sys.objects WHERE name = 'YourObjectName'. Убедитесь, что временные таблицы создаются в той же сессии.","tags":["invalid object","invalid object name","table not found","table does not exist","schema","app schema","208"]},{"code":"229","title":"Отказано в разрешении SELECT/EXECUTE на объект","severity":"caution","meaning":"У пользователя нет разрешения на выполнение запрошенной операции (SELECT, INSERT, EXECUTE и т. д.) над объектом. Доступ запрещён по соображениям безопасности.","cause":"Необходимое разрешение не выдано пользователю/роли, неверное владение схемой (нарушенная цепочка владения), явный DENY на объект или пользователь не входит в роль с достаточными правами.","fix":"Выдайте необходимое разрешение: GRANT SELECT ON dbo.MyTable TO UserName (или GRANT EXECUTE ON dbo.MyProcedure TO ...). Добавьте пользователя в подходящую роль (например, db_datareader). Удалите явный DENY, если он есть. Организуйте разрешения по принципу наименьших привилегий. Проведите аудит существующих разрешений через sys.database_permissions.","tags":["permission denied","access denied","grant","privilege","security","security permissions","229"]},{"code":"4060","title":"Не удаётся открыть базу данных, запрошенную логином. Ошибка входа","severity":"danger","meaning":"Аутентификация может пройти успешно, но подключение отклоняется, поскольку запрошенную базу данных невозможно открыть. Обычно наблюдается вместе с 18456.","cause":"База данных не существует, находится в состоянии OFFLINE/SINGLE_USER/RESTORING/SUSPECT, у логина нет сопоставленного пользователя в этой базе данных или база данных по умолчанию в строке подключения недоступна.","fix":"Убедитесь, что база данных существует и находится в состоянии ONLINE: SELECT name, state_desc FROM sys.databases. Создайте в базе данных пользователя для логина и выдайте разрешения. Проверьте значение Initial Catalog/Database в строке подключения. Если база данных в состоянии RESTORING/SUSPECT, выполните шаги ремонта/восстановления.","tags":["cannot open database","database cannot be opened","4060","login","state","offline"]},{"code":"233","title":"Соединение с сервером успешно установлено, но затем произошла ошибка (на другом конце канала нет процесса)","severity":"danger","meaning":"Клиент достиг сервера, но соединение разорвалось во время рукопожатия. Обычно причина — конфигурация Named Pipes/TCP, настройки протоколов или отклонение на стороне сервера. Связано с 10054 (соединение принудительно закрыто).","cause":"Отключены протоколы Shared Memory/Named Pipes/TCP, не разрешены удалённые подключения, учётная запись не может войти (см. 18456), межсетевой экран/обрыв сети или сервер принудительно закрывает соединение (10054).","fix":"Включите TCP/IP и при необходимости Named Pipes в SQL Server Configuration Manager и перезапустите службу. Убедитесь, что удалённые подключения разрешены. Откройте порт SQL (по умолчанию 1433) в межсетевом экране. При проблемах с аутентификацией изучите код State ошибки 18456 в журнале ошибок. Проверьте стабильность сети и настройки TLS/шифрования.","tags":["connection","connection error","pipe","10054","tcp","named pipes","233","network"]},{"code":"823","title":"Операционная система вернула SQL Server ошибку при чтении/записи по смещению в файле (ошибка I/O)","severity":"danger","meaning":"SQL Server получил ошибку I/O на уровне операционной системы при чтении или записи файла данных/журнала. Это серьёзное предупреждение о целостности, указывающее на проблему с дисковым оборудованием или подсистемой хранения.","cause":"Сбойные сектора диска, неисправный контроллер/драйвер, проблемы хранилища/SAN, ошибки файловой системы или страница, которую вообще невозможно прочитать (жёсткая ошибка I/O). Обычно связано с оборудованием.","fix":"Немедленно сделайте корректный бэкап (если возможно). Изучите журнал событий Windows и журналы хранилища, протестируйте исправность оборудования/дисков. Запустите DBCC CHECKDB для проверки целостности. Проверьте таблицу suspect_pages (msdb.dbo.suspect_pages). При наличии повреждения восстановите данные из корректного бэкапа или выполните восстановление на уровне страниц. Замените/отремонтируйте оборудование.","tags":["io error","i/o error","disk","corruption","data corruption","823","checkdb"]},{"code":"824","title":"SQL Server обнаружил ошибку I/O, связанную с логической согласованностью (повреждение страницы)","severity":"danger","meaning":"SQL Server прочитал страницу, но её содержимое логически несогласованно (неверная контрольная сумма, torn page или неверный ID страницы). Страница данных повреждена.","cause":"Ошибки подсистемы хранения, внезапное отключение питания/torn pages, проблемы контроллера или драйвера, скрытое повреждение, обнаруженное PAGE_VERIFY CHECKSUM. Как и 823, в основном связано с оборудованием/хранилищем.","fix":"Запустите DBCC CHECKDB, чтобы определить масштаб повреждения. Восстановите данные из последнего корректного бэкапа или восстановите затронутые страницы на уровне страниц. Изучите msdb.dbo.suspect_pages. Убедитесь, что включён PAGE_VERIFY = CHECKSUM. Проверьте оборудование/хранилище. REPAIR_ALLOW_DATA_LOSS должен быть последним средством и приводит к потере данных.","tags":["page corruption","corrupt page","consistency","824","checkdb","checksum","torn page"]},{"code":"701","title":"Недостаточно системной памяти в пуле ресурсов для выполнения этого запроса","severity":"danger","meaning":"SQL Server не смог выделить из соответствующего пула ресурсов память, необходимую для выполнения запроса. Операция завершилась ошибкой из-за нехватки памяти.","cause":"Недостаточно памяти сервера, низкое значение max server memory, запросы с большим потреблением памяти (крупные сортировки/хеширования), утечка памяти, множество параллельных запросов или ограничения пулов Resource Governor.","fix":"Пересмотрите настройку max server memory и обеспечьте достаточный объём физической RAM. Оптимизируйте запросы с большим потреблением памяти (подходящие индексы, меньше сортировок/хеширований). Отслеживайте память: sys.dm_os_memory_clerks, DBCC MEMORYSTATUS, счётчики памяти Perfmon. Настройте ограничения пулов Resource Governor. При необходимости снизьте параллельность.","tags":["memory","memory pressure","insufficient memory","resource pool","701","oom"]},{"code":"845","title":"Истёк тайм-аут ожидания buffer latch","severity":"danger","meaning":"Истёк тайм-аут ожидания buffer latch на странице данных. SQL Server не смог за разумное время получить кратковременную внутреннюю блокировку (latch), необходимую для доступа к странице; обычно это признак высокой нагрузки на I/O или нехватки памяти.","cause":"Медленная/перегруженная дисковая подсистема, постоянное чтение страниц с диска из-за нехватки памяти, проблемы оборудования, чрезмерный paging или узкое место в хранилище.","fix":"Измерьте задержку дискового I/O (sys.dm_io_virtual_file_stats, задержка дисков в Perfmon). Проверьте производительность хранилища и исправность оборудования. Проверьте достаточность памяти и настройку max server memory. Изучите типы ожиданий (sys.dm_os_wait_stats, ожидания PAGEIOLATCH). Устраните узкое место в хранилище; при необходимости обеспечьте более быстрые диски/больше RAM.","tags":["buffer latch","latch timeout","845","io","pageiolatch","memory","memory pressure"]},{"code":"3201","title":"Не удаётся открыть устройство бэкапа. Ошибка операционной системы (ошибка устройства бэкапа)","severity":"caution","meaning":"При бэкапе или восстановлении не удалось открыть целевое устройство/файл бэкапа. SQL Server не смог получить доступ к указанному пути.","cause":"Неверный/несуществующий путь к папке, отсутствие у служебной учётной записи SQL Server права записи в целевую папку, нет доступа к сетевому ресурсу (UNC), заполненный диск или файл заблокирован другим процессом.","fix":"Убедитесь, что путь для бэкапа существует и указан правильно. Выдайте служебной учётной записи SQL Server право записи в целевую папку. Для ресурсов UNC проверьте доступ служебной учётной записи. Убедитесь, что на диске достаточно свободного места. Для локального пути убедитесь, что буква диска видна с сервера (а не диск клиента).","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)","severity":"caution","meaning":"Вставка или обновление попытались записать в поле (поля), защищённые уникальным индексом, значение, которое уже существует в коллекции. MongoDB отклоняет дублирующийся ключ.","cause":"Коллизия _id, повторная запись того же значения в поле с уникальным индексом, состояние гонки при upsert или коллизия значений null у нескольких документов в уникальном индексе (не sparse-индекс).","fix":"Перед записью проверьте, существует ли документ, или используйте upsert: db.collection.updateOne(filter, {$set:{...}}, {upsert:true}). Посмотрите имя индекса и значение dup key в сообщении об ошибке. Если уникальность на самом деле не нужна, удалите индекс; если проблема в null, используйте частичный/sparse-индекс. В приложении перехватывайте ошибку и повторяйте операцию либо сообщайте о ней пользователю.","tags":["duplicate key","duplicate key","unique index","unique","e11000","upsert","_id"]},{"code":"11000","title":"DuplicateKey (код ошибки 11000)","severity":"caution","meaning":"Числовой код ошибки дублирующегося ключа (возвращается вместе с текстом E11000). Операция записи нарушила ограничение уникального индекса.","cause":"Повторная запись того же уникального поля, параллельные upsert, генерирующие один и тот же ключ, или дублирующиеся документы в массовой записи.","fix":"Перехватывайте код ошибки 11000 в коде приложения и реализуйте идемпотентное поведение (обновить, если существует, иначе вставить). В массовых операциях используйте ordered:false, чтобы документы, кроме дубликатов, были записаны. Пересмотрите правило уникальности и определение индекса.","tags":["duplicatekey","11000","duplicate","unique","unique","bulk write","write error"]},{"code":"13","title":"Unauthorized (код ошибки 13)","severity":"caution","meaning":"У аутентифицированного пользователя нет привилегии на выполняемую операцию или ресурс. Пользователь вошёл в систему, но его роли не охватывают это действие.","cause":"Пользователю не выдана необходимая роль (например, readWrite, dbAdmin), операция выполняется в неверной базе данных или привилегий недостаточно для административной команды. Неполная конфигурация RBAC.","fix":"Проверьте роли пользователя: db.getUser(\"app_user\"). Выдайте необходимую роль: db.grantRolesToUser(\"app_user\", [{role:\"readWrite\", db:\"mydb\"}]). Убедитесь, что операция выполняется в правильной базе данных. Назначьте подходящую встроенную или пользовательскую роль по принципу наименьших привилегий. Проверьте значение authSource в строке подключения.","tags":["unauthorized","unauthorized","authorization","role","role","rbac","13","permission"]},{"code":"18","title":"AuthenticationFailed (код ошибки 18)","severity":"caution","meaning":"Аутентификация не пройдена. Указанные имя пользователя, пароль или учётные данные недействительны; пользователь не смог войти.","cause":"Неверные имя пользователя/пароль, неверная база данных authSource, несовпадающий механизм аутентификации (SCRAM-SHA-1/SCRAM-SHA-256) или пользователь не определён в указанной базе данных.","fix":"Проверьте имя пользователя и пароль. Укажите правильный authSource в строке подключения (база данных, в которой создан пользователь, обычно admin): ...?authSource=admin. Убедитесь, что механизм аутентификации совместим с сервером. Убедитесь, что пользователь создан в правильной базе данных. Если пароль содержит специальные символы, закодируйте его для URL (URL-encode).","tags":["authentication failed","authentication","password","password","authsource","18","scram"]},{"code":"11600","title":"InterruptedAtShutdown (код ошибки 11600)","severity":"danger","meaning":"Операция прервана, поскольку сервер начал завершение работы. Выполняемая операция не смогла завершиться, так как процесс mongod останавливался.","cause":"Перезапуск/остановка mongod, остановка при stepdown primary в replica set, плановое обслуживание, сбой или завершение процесса OOM killer.","fix":"Дождитесь восстановления сервера и повторите операцию. В приложении перехватывайте временные ошибки и автоматически повторяйте операции; включите retryable writes. При неожиданных остановках изучите журналы mongod и системные журналы (OOM, сбой). Для высокой доступности используйте replica set.","tags":["interrupted","shutdown","shutdown","downtime","11600","retry","replica set"]},{"code":"50","title":"MaxTimeMSExpired (код ошибки 50)","severity":"caution","meaning":"Сервер отменил операцию, поскольку она превысила лимит времени, заданный через maxTimeMS. Запрос не завершился за отведённое время.","cause":"Длительные запросы, сканирования коллекций (COLLSCAN) из-за отсутствующих индексов, большие объёмы данных, тяжёлые операции агрегации или высокая нагрузка на сервер.","fix":"Оптимизируйте запрос: изучите план с помощью explain() и добавьте подходящий индекс. Если действительно нужно больше времени, увеличьте maxTimeMS. Упростите тяжёлые агрегации или фильтруйте раньше с помощью $match. Разбивайте результаты на страницы. Проверьте нагрузку на сервер и ёмкость ресурсов.","tags":["maxtimems","timeout","timeout","50","slow query","slow query","index"]},{"code":"251","title":"NoSuchTransaction (код ошибки 251)","severity":"caution","meaning":"Упомянутая многодокументная транзакция не найдена. Транзакция уже завершилась, истёк её тайм-аут или использован недопустимый номер транзакции.","cause":"Транзакция превысила transactionLifetimeLimitSeconds и была автоматически прервана, сессия завершилась, неверный/устаревший txnNumber или продолжение транзакции, уже прерванной после ошибки.","fix":"Начните транзакцию заново (с новой сессией). Делайте транзакции короткими; проектируйте их так, чтобы они укладывались в время жизни по умолчанию (60 s). При ошибках с меткой TransientTransactionError безопасно повторяйте всю транзакцию. Если нужны длительные транзакции, пересмотрите настройку transactionLifetimeLimitSeconds.","tags":["nosuchtransaction","transaction","transaction","251","session","abort","retry"]},{"code":"112","title":"WriteConflict (код ошибки 112)","severity":"caution","meaning":"Конфликт записи, возникающий, когда две параллельные операции пытаются изменить один и тот же документ (документы). Из-за оптимистичного управления параллелизмом WiredTiger откатывает одну из операций.","cause":"Высокопараллельная запись в одни и те же документы, конфликтующие обновления во время длительных транзакций, сильная конкуренция за горячий документ.","fix":"Повторите операцию; WriteConflict обычно временная ошибка и устраняется повтором (она помечена TransientTransactionError). Делайте транзакции короткими и сокращайте число затрагиваемых документов. Пересмотрите модель данных, чтобы снизить конкуренцию за один документ. Реализуйте в приложении повторы с экспоненциальной задержкой.","tags":["writeconflict","write conflict","112","concurrency","concurrency","retry","wiredtiger"]},{"code":"133","title":"FailedToSatisfyReadPreference (код ошибки 133)","severity":"danger","meaning":"Не найден подходящий член replica set, удовлетворяющий заданному read preference (например, primary, secondary). Драйвер не смог выбрать узел для направления запроса.","cause":"Члены replica set недоступны/отключены, primary отсутствует во время выборов, сетевое разделение, ни один член не соответствует критериям tag set или не удаётся достичь большинства (кворума).","fix":"Проверьте состояние replica set: rs.status(). Проверьте исправность узлов и сетевую связность. Дождитесь завершения выборов primary и повторите операцию. Пересмотрите настройку read preference; при необходимости используйте более гибкий вариант (например, primaryPreferred). Если используете tag sets, убедитесь, что существует подходящий член.","tags":["read preference","readpreference","133","replica set","election","secondary","primary"]},{"code":"6","title":"HostUnreachable (код ошибки 6)","severity":"danger","meaning":"Драйвер/клиент не смог достичь целевого сервера MongoDB (хоста). Соединение на сетевом уровне установить не удалось.","cause":"Сервер не работает, неверный хост/порт, блокировка межсетевым экраном, проблема разрешения DNS, обрыв сети или конфигурация bindIp, исключающая клиента.","fix":"Убедитесь, что имя хоста и порт указаны правильно (по умолчанию 27017). Убедитесь, что служба mongod запущена. Проверьте межсетевой экран и сетевой доступ (ping/telnet). Убедитесь, что настройка bindIp в mongod.conf разрешает доступ клиента. Проверьте записи DNS/SRV (для подключений mongodb+srv).","tags":["hostunreachable","host","connection","network","network","6","firewall","bindip"]},{"code":"89","title":"NetworkTimeout (код ошибки 89)","severity":"danger","meaning":"Сетевая операция при обмене данными с сервером не завершилась за отведённое время, и истёк тайм-аут. Соединение было установлено, но ответ не пришёл вовремя.","cause":"Медленная или нестабильная сеть, перегруженный сервер, длительная операция, задерживающая ответ, слишком низкие значения тайм-аутов socket/connect или перегрузка сети.","fix":"Измерьте сетевую задержку и стабильность. Проверьте нагрузку и ресурсы сервера. Задайте в драйвере реалистичные значения connectTimeoutMS и socketTimeoutMS. Перехватывайте временные ошибки и повторяйте операции. Оптимизируйте медленные запросы, чтобы сократить время ответа.","tags":["networktimeout","network timeout","89","timeout","socket","network","retry"]},{"code":"262","title":"ExceededTimeLimit (код ошибки 262)","severity":"caution","meaning":"Операция превысила лимит времени, допустимый на стороне сервера, и была прервана. Эту ошибку может вызвать превышение maxTimeMS или внутренних лимитов времени.","cause":"Длительный запрос/команда, медленное выполнение из-за отсутствующего индекса, тяжёлая агрегация, сканирование большого набора данных или высокая нагрузка на сервер.","fix":"Изучите запрос с помощью explain() и добавьте подходящий индекс. Разбейте операцию на более мелкие части. Задайте maxTimeMS в соответствии с реальной потребностью. Сокращайте данные на ранних этапах конвейера агрегации с помощью $match/$project. Убедитесь, что ресурсов сервера достаточно.","tags":["exceededtimelimit","time limit","262","timeout","maxtimems","slow","index"]},{"code":"292","title":"QueryExceededMemoryLimitNoDiskUseAllowed (код ошибки 292)","severity":"caution","meaning":"Запрос или этап агрегации превысил допустимый лимит памяти (по умолчанию 100 MB) и завершился ошибкой, поскольку запись на диск не разрешена. Обычно наблюдается на этапах сортировки или группировки.","cause":"Сортировка в памяти по большим неиндексированным данным, крупные этапы агрегации $group/$sort, очень большие промежуточные наборы результатов, настройка allowDiskUse:false.","fix":"Разрешите сброс на диск в агрегации с помощью параметра allowDiskUse:true: db.collection.aggregate(pipeline, {allowDiskUse:true}). Сократите использование памяти, проиндексировав сортируемые поля. Фильтруйте конвейер на раннем этапе с помощью $match, чтобы сократить объём обрабатываемых данных. При необходимости разбивайте результаты на страницы. В MongoDB 6.0+ использование диска в большинстве случаев включено по умолчанию.","tags":["memory limit","memory limit","292","allowdiskuse","sort","aggregation","group"]},{"code":"26","title":"NamespaceNotFound (код ошибки 26)","severity":"safe","meaning":"Указанное пространство имён (database.collection) не найдено. Заданная коллекция или индекс не существует.","cause":"Опечатка в имени коллекции, коллекция ещё не создана, неверный контекст базы данных или попытка выполнить drop/операцию с индексом для несуществующей коллекции.","fix":"Проверьте имя коллекции и базу данных: show collections. Помните, что имена чувствительны к регистру. MongoDB создаёт коллекцию автоматически при первой записи; при необходимости создайте её явно с помощью db.createCollection(). Для операций, таких как drop, заранее проверяйте, что коллекция существует.","tags":["namespacenotfound","namespace","collection missing","26","collection","not found","not found"]},{"code":"43","title":"CursorNotFound (код ошибки 43)","severity":"caution","meaning":"Серверный курсор больше не существует. Когда клиент запрашивает следующий пакет (getMore), возвращается ошибка, поскольку курсор не найден.","cause":"Истёк тайм-аут курсора (по умолчанию 10 минут бездействия), слишком долгое ожидание между пакетами, перезапуск сервера или курсор был закрыт явно.","fix":"Обрабатывайте результаты курсора быстрее; не ждите долго между пакетами. Для длительных операций осторожно используйте параметр noCursorTimeout (mongosh: cursor.noCursorTimeout(); PyMongo: no_cursor_timeout=True) и всегда закрывайте курсор; курсор внутри сессии закрывается по тайм-ауту сессии (localLogicalSessionTimeoutMinutes, по умолчанию 30 min) даже с этим параметром. Для больших наборов результатов выполняйте итерацию с меньшим batchSize или используйте постраничную выборку по диапазонам. Перезапустите операцию и выполните запрос заново.","tags":["cursornotfound","cursor","cursor","43","getmore","timeout","batch","pagination"]},{"code":"211","title":"KeyNotFound (код ошибки 211)","severity":"caution","meaning":"Ожидаемый ключ/значение не найден. Обычно указывает на несогласованность, связанную с внутренним управлением ключами, ключами сессий или коллекцией oplog/keys.","cause":"Отсутствующие/удалённые ключи подписи в коллекции admin.system.keys, проблемы синхронизации часов в replica set/шардированном кластере, проблемы keyfile/ротации ключей или повреждённое внутреннее состояние.","fix":"Проверьте синхронизацию часов (NTP) между членами replica set. Проверьте содержимое admin.system.keys и генерацию ключей. Проверьте состояние кластера (rs.status(), sh.status()). Если ошибка сохраняется, проанализируйте журналы mongod и пересмотрите конфигурацию keyfile/идентификации. Убедитесь, что primary работает исправно, чтобы ключи могли быть сгенерированы заново.","tags":["keynotfound","key","211","keys","session","ntp","cluster","cluster"]}]},"errors/redis.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"WRONGTYPE","title":"WRONGTYPE: операция над ключом, содержащим значение неподходящего типа","severity":"caution","meaning":"Команда выполнена для ключа, тип данных которого ей не подходит. Например, применение команды для списка (LPUSH) или хеша (HSET) к ключу, содержащему строку.","cause":"Ключ имеет другой тип, чем ожидалось, одно и то же имя ключа используется с разными типами данных, путаница типов в логике приложения или перезапись старого/оставшегося ключа.","fix":"Проверьте тип ключа: TYPE mykey. Выбирайте команду в соответствии с фактическим типом ключа. Различайте типы в именовании ключей (например, set для user:1:tags, string для user:1:name). При необходимости удалите старый ключ (DEL) и создайте его заново с правильным типом. Обеспечьте согласованность типов на уровне приложения.","tags":["wrongtype","wrong type","type","data type","key","key"]},{"code":"OOM","title":"OOM: команда запрещена, когда используемая память > 'maxmemory'","severity":"danger","meaning":"Память, используемая Redis, достигла лимита maxmemory, и команды записи отклоняются, поскольку политика вытеснения не может освободить место для новых данных. Память заполнена; запись невозможна.","cause":"Превышен лимит maxmemory, выбрана политика noeviction (ключи никогда не удаляются), данные без TTL продолжают расти, утечка памяти или недостаточно RAM для лимита.","fix":"Проверьте использование памяти: INFO memory и MEMORY USAGE. Выберите подходящую политику вытеснения (например, allkeys-lru или volatile-lru): CONFIG SET maxmemory-policy allkeys-lru. Задавайте ключам TTL, чтобы они удалялись автоматически. Увеличьте maxmemory/RAM. Найдите и уменьшите большие ключи (redis-cli --bigkeys). Удалите ненужные данные.","tags":["oom","out of memory","memory","maxmemory","eviction","memory","danger"]},{"code":"NOAUTH","title":"NOAUTH: требуется аутентификация","severity":"caution","meaning":"Redis требует аутентификации, но клиент попытался выполнить команду до её прохождения. Сначала необходимо войти с помощью AUTH.","cause":"На сервере задан пароль requirepass/ACL, клиент не отправляет команду AUTH, пароль отсутствует в строке подключения или AUTH не повторяется после повторного установления соединения.","fix":"Проходите аутентификацию перед выполнением команд: AUTH password (с ACL — AUTH app_user password). Добавьте пароль в клиент/строку подключения. Если используете пул подключений, убедитесь, что AUTH выполняется на каждом новом подключении. Храните пароль безопасно (переменная окружения/менеджер секретов).","tags":["noauth","authentication","authentication","auth","password","requirepass","acl"]},{"code":"WRONGPASS","title":"WRONGPASS: неверная пара имя пользователя-пароль или пользователь отключён","severity":"caution","meaning":"Пара имя пользователя/пароль, указанная в команде AUTH, недействительна, или пользователь ACL отключён. Аутентификация отклонена.","cause":"Неверный пароль, неверное имя пользователя ACL, пользователь отключён в ACL, изменилось значение requirepass или проблема экранирования/кодировки в пароле.","fix":"Введите правильные имя пользователя и пароль. Проверьте пользователя ACL: ACL LIST и ACL GETUSER app_user. Если пользователь отключён, включите его (ACL SETUSER app_user on ...). Проверьте пароль, заданный через requirepass (CONFIG GET requirepass). При сбросе пароля убедитесь, что все клиенты обновлены.","tags":["wrongpass","password","invalid password","auth","acl","user","invalid"]},{"code":"READONLY","title":"READONLY: запись в реплику только для чтения невозможна","severity":"caution","meaning":"Команда записи отправлена на узел-реплику (slave). Реплики по умолчанию доступны только для чтения; запись возможна только на master.","cause":"Клиент подключается к реплике вместо master, старый master стал репликой после failover, ошибочная маршрутизация на неверный узел в Sentinel/Cluster или балансировщик нагрузки, перенаправляющий запись на реплику.","fix":"Направляйте запросы на запись на узел master. Если используете Sentinel, запрашивайте текущий master у Sentinel (SENTINEL get-master-addr-by-name). В Cluster пишите в master нужного шарда. Настройте разделение чтения/записи в клиентской библиотеке. После failover обновите подключения, чтобы они указывали на новый master.","tags":["readonly","read only","replica","slave","write","write","master","failover"]},{"code":"MASTERDOWN","title":"MASTERDOWN: связь с MASTER разорвана, а replica-serve-stale-data имеет значение 'no'","severity":"danger","meaning":"Связь между репликой и master разорвана, и поскольку replica-serve-stale-data имеет значение 'no', реплика отказывается отвечать на запросы устаревшими данными.","cause":"Узел master аварийно завершился или недоступен, обрыв/разделение сети, разрыв канала репликации master-реплика или master не может отвечать под высокой нагрузкой.","fix":"Проверьте состояние узла master и сетевую связность. Проверьте состояние репликации: INFO replication. Восстановите master или дайте выполнить failover через Sentinel/Cluster. Если выдача устаревших данных допустима, рассмотрите replica-serve-stale-data yes (учитывайте риск для согласованности). Для высокой доступности используйте Sentinel/Cluster.","tags":["masterdown","master down","replication","replication","stale","failover","sentinel","danger"]},{"code":"CLUSTERDOWN","title":"CLUSTERDOWN: кластер не работает / hash slot не обслуживается","severity":"danger","meaning":"Redis Cluster работает не полностью. Некоторые hash slots не обслуживаются ни одним узлом, поэтому кластер отклоняет запросы.","cause":"Доступно недостаточно узлов master, один или несколько слотов не назначены/не покрыты, потеря большинства (кворума), сбои узлов или недоступность части слотов при cluster-require-full-coverage yes.","fix":"Проверьте состояние кластера: CLUSTER INFO и CLUSTER NODES. Верните отказавшие master в строй или дайте повысить реплики. Переназначьте неназначенные слоты (redis-cli --cluster fix или CLUSTER ADDSLOTS). Убедитесь, что все слоты покрыты. Если частичная работа допустима, рассмотрите cluster-require-full-coverage no.","tags":["clusterdown","cluster","cluster","hash slot","slot","quorum","coverage","danger"]},{"code":"LOADING","title":"LOADING: Redis загружает набор данных в память","severity":"caution","meaning":"Redis запускается и загружает набор данных (RDB/AOF) с диска в память. Большинство команд временно отклоняется до завершения загрузки.","cause":"Сервер только что запущен, загружается большой файл RDB/AOF, реплика выполняет полную синхронизацию с master или идёт процесс восстановления после перезапуска.","fix":"Дождитесь окончания загрузки; длительность зависит от объёма данных. Отслеживайте состояние: INFO persistence (ждите, пока loading:1; готово при 0). В приложении перехватывайте эту временную ошибку и повторяйте запрос с короткими интервалами. Чтобы сократить время загрузки, пересмотрите стратегию набора данных/персистентности (RDB или AOF). Обеспечьте достаточный объём RAM и быстрые диски.","tags":["loading","loading","startup","startup","rdb","aof","sync","persistence"]},{"code":"BUSY","title":"BUSY: Redis занят выполнением скрипта. Можно вызвать только SCRIPT KILL или SHUTDOWN NOSAVE","severity":"caution","meaning":"Выполняется длительный скрипт или функция Lua, превысившие busy-reply-threshold (до Redis 7 назывался lua-time-limit; по умолчанию 5000 ms). Поскольку Redis однопоточный, в это время он не может обрабатывать другие команды; принимаются только SCRIPT KILL (FUNCTION KILL для функций) или SHUTDOWN NOSAVE.","cause":"Бесконечный цикл или очень долго выполняющийся скрипт/функция Lua, тяжёлые вычисления, скрипт, перебирающий большие данные, или неудачно написанный вызов EVAL.","fix":"Если скрипт не изменял данные, остановите его: SCRIPT KILL. Если скрипт уже выполнил запись, SCRIPT KILL не сработает; в качестве последнего средства для сохранения согласованности данных может потребоваться SHUTDOWN NOSAVE (несохранённые данные теряются; будьте осторожны). Пишите короткие и детерминированные скрипты. Разбивайте длительные операции на части. Пересмотрите настройку busy-reply-threshold (ранее lua-time-limit).","tags":["busy","busy server","lua","script","script kill","eval","time limit"]},{"code":"MISCONF","title":"MISCONF: Redis настроен на сохранение снимков RDB, но сейчас не может сохранять данные на диск","severity":"danger","meaning":"Redis настроен на создание снимков RDB, но не может писать на диск. В этом состоянии при включённом stop-writes-on-bgsave-error Redis отклоняет все команды записи.","cause":"Заполненный диск, проблемы с правами файловой системы, каталог без права записи, ошибки фонового сохранения (BGSAVE), сбой fork из-за нехватки памяти или проблема дискового оборудования.","fix":"Освободите место на диске и проверьте право записи в каталог RDB (dir). Изучите последнюю ошибку BGSAVE: INFO persistence и журналы Redis. Чтобы временно разрешить запись до устранения проблемы, можно задать stop-writes-on-bgsave-error no (учитывайте риск для надёжности). Обеспечьте достаточно памяти для fork и настройку vm.overcommit_memory=1. Проверьте конфигурацию персистентности (save/appendonly).","tags":["misconf","persist","durability","rdb","bgsave","disk","snapshot","danger"]},{"code":"CROSSSLOT","title":"CROSSSLOT: ключи в запросе не попадают в один и тот же слот","severity":"caution","meaning":"В Redis Cluster использована команда с несколькими ключами (например, MGET, MSET, SUNION), но ключи попадают в разные hash slots. Операции с несколькими ключами требуют, чтобы все ключи находились в одном слоте.","cause":"В режиме кластера команды, обращающиеся сразу к нескольким ключам, вызываются с ключами, распределёнными по разным слотам. Ключи распределяются по разным узлам, поскольку не используют hash tag.","fix":"Используйте hash tag, чтобы поместить связанные ключи в один слот: закрепите часть имени ключа фигурными скобками (например, {user1}:profile и {user1}:settings попадают в один слот). Используйте команды с несколькими ключами только для ключей с одинаковым hash tag. В качестве альтернативы разбейте операции на команды с одним ключом.","tags":["crossslot","cross slot","cluster","cluster","hash tag","slot","mget","mset"]},{"code":"MOVED","title":"MOVED slot ip:port (перенаправление в кластере)","severity":"safe","meaning":"В Redis Cluster hash slot, содержащий запрошенный ключ, принадлежит другому узлу. Сервер постоянно перенаправляет клиента на нужный узел. Это перенаправление, а не ошибка.","cause":"Клиент без поддержки кластера отправляет запрос на неверный узел, слоты перераспределены между узлами (решардинг) или карта слотов, кэшированная клиентом, устарела.","fix":"Используйте клиентскую библиотеку с поддержкой кластера; такие библиотеки автоматически следуют ответу MOVED, перенаправляют на нужный узел и обновляют карту слотов. При подключении через redis-cli используйте флаг -c. Убедитесь, что клиент периодически обновляет топологию слотов (CLUSTER SLOTS/SHARDS).","tags":["moved","cluster","cluster","redirect","redirect","slot","resharding"]},{"code":"ASK","title":"ASK slot ip:port (перенаправление в кластере)","severity":"safe","meaning":"В Redis Cluster слот переносится на другой узел, и запрошенный ключ временно может находиться на целевом узле. Сервер перенаправляет клиента на целевой узел только для этого запроса (временное перенаправление).","cause":"Обращение к ключу, который уже перенесён в рамках слота, мигрирующего во время решардинга. В отличие от MOVED, перенаправление не постоянное; оно действует только в данный момент.","fix":"Клиенты с поддержкой кластера обрабатывают перенаправления ASK автоматически: они отправляют команду ASKING на целевой узел и повторяют запрос, но не обновляют карту слотов навсегда. Используйте подходящую клиентскую библиотеку, а для redis-cli — флаг -c. По завершении решардинга перенаправления становятся MOVED и постоянными.","tags":["ask","asking","cluster","cluster","migration","migration","redirect","redirect"]},{"code":"NOSCRIPT","title":"NOSCRIPT: подходящий скрипт не найден. Используйте EVAL","severity":"caution","meaning":"Дайджест SHA1 скрипта Lua, вызванного через EVALSHA, не найден в кэше скриптов сервера. Redis не знает скрипта, соответствующего этому хешу.","cause":"Сервер был перезапущен или кэш скриптов очищен командой SCRIPT FLUSH, скрипт никогда не загружался или кэш пуст на новом узле после failover.","fix":"Выполните скрипт с полным телом через EVAL (Redis кэширует его автоматически) или сначала загрузите его через SCRIPT LOAD и используйте возвращённый SHA в EVALSHA. В приложении перехватывайте ошибку NOSCRIPT и реализуйте резервную логику перехода с EVALSHA на EVAL. Многие клиентские библиотеки Redis делают это автоматически.","tags":["noscript","script","evalsha","eval","lua","sha","script load","cache"]},{"code":"EXECABORT","title":"EXECABORT: транзакция отменена из-за предыдущих ошибок","severity":"caution","meaning":"Транзакция в блоке MULTI/EXEC прервана из-за ошибок, возникших на этапе постановки в очередь. EXEC отклоняет транзакцию, не выполняя ни одной команды.","cause":"Команда между MULTI и EXEC содержит синтаксическую ошибку или ошибки, обнаруженные при постановке в очередь, например несуществующее имя команды или неверное число аргументов. Такие ошибки делают недействительной всю транзакцию.","fix":"Исправьте синтаксис и аргументы команд в блоке MULTI. Проверяйте ответы при постановке команд в очередь; если какая-либо команда возвращает ошибку вместо QUEUED, транзакция прерывается на EXEC. Соберите транзакцию заново из корректных команд. Примечание: транзакции Redis не откатываются; к EXECABORT приводят только ошибки постановки в очередь, а ошибки выполнения не останавливают остальные команды.","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]] (до 7.x: FORBIDDEN/12/index read-only / allow delete (api))","severity":"danger","meaning":"Поскольку использование диска превысило flood stage watermark (по умолчанию 95%; на больших дисках определяется max_headroom), Elasticsearch установил блокировку read_only_allow_delete на индексы, у которых есть шард на затронутом узле, чтобы диск не заполнился полностью. Из-за этой блокировки запись в индекс (index/update/delete документа) невозможна; разрешено только удаление самого индекса. ES 8.x возвращает эту ошибку с HTTP 429.","cause":"Использование диска хотя бы на одном узле превысило порог cluster.routing.allocation.disk.watermark.flood_stage. Даже если блокировку снять вручную, Elasticsearch установит её снова, пока диск остаётся заполненным. Типичные причины — большие индексы без ротации журналов, неочищенные snapshots или недостаточная ёмкость диска.","fix":"Сначала освободите место на диске: удалите старые/ненужные индексы (DELETE /old-index), сделайте snapshot и удалите индексы или увеличьте диск. В 7.4+ блокировка снимается автоматически, когда использование диска опускается ниже high watermark; чтобы снять её сразу, не дожидаясь, удалите её вручную: PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. Для постоянного решения автоматически удаляйте/архивируйте старые данные с помощью ILM (Index Lifecycle Management) и пересмотрите настройки watermark (disk.watermark.low/high/flood_stage).","tags":["disk","read-only","watermark","flood-stage","cluster-block","danger"]},{"code":"index_read_only_allow_delete","title":"TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block (устаревший вариант: FORBIDDEN/12/index read-only / allow delete (api))","severity":"danger","meaning":"Индекс доступен только для чтения, поскольку index.blocks.read_only_allow_delete имеет значение true; новые документы нельзя записать или обновить, можно только удалить индекс. Обычно Elasticsearch устанавливает это автоматически при превышении flood stage watermark диска; ES 8.x возвращает HTTP 429 на запросы записи.","cause":"Блокировка установлена автоматически, поскольку использование диска превысило порог flood stage, или параметр установлен в true вручную. Если снять блокировку, не освободив место на диске, Elasticsearch установит её снова.","fix":"Освободите достаточно места на диске. В 7.4+ автоматически установленная блокировка снимается сама, когда использование диска опускается ниже high watermark; для блокировки, установленной вручную, или чтобы снять её не дожидаясь, удалите блокировку: PUT /_all/_settings {\"index.blocks.read_only_allow_delete\": null}. Для одного индекса: PUT /index-name/_settings {\"index.blocks.read_only_allow_delete\": null}. Если ситуация повторяется, увеличьте ёмкость диска, настройте политику ILM и отслеживайте с помощью _cat/allocation, какой узел заполняется.","tags":["read-only","disk","forbidden","watermark","block","danger"]},{"code":"high_disk_watermark_exceeded","title":"Превышен high disk watermark [90%] / превышен flood stage disk watermark [95%]","severity":"danger","meaning":"Использование диска узла превысило порог high watermark (по умолчанию 90%) или flood stage (по умолчанию 95%). При превышении high watermark Elasticsearch пытается перенести шарды с этого узла на другие; при превышении flood stage переводит индексы в режим только для чтения.","cause":"Диск заполняется: постоянно растущие индексы журналов/метрик, никогда не удаляемые старые индексы, неочищенный репозиторий snapshots, диск недостаточного размера или переполнение одного узла из-за несбалансированного распределения шардов.","fix":"Проверьте использование диска: GET _cat/allocation?v. Удалите старые индексы или сделайте snapshot и удалите их, увеличьте диск. Пороги можно временно повысить (осторожно; в 8.x задавайте все три вместе из-за проверки max_headroom): PUT _cluster/settings {\"persistent\":{\"cluster.routing.allocation.disk.watermark.low\":\"88%\",\"cluster.routing.allocation.disk.watermark.high\":\"93%\",\"cluster.routing.allocation.disk.watermark.flood_stage\":\"96%\"}}. После устранения проблемы верните значения по умолчанию, задав тем же параметрам null (transient-настройки устарели; не используйте их). Постоянное решение: управление жизненным циклом с ILM, регулярная очистка индексов и планирование ёмкости. Если индексы стали только для чтения из-за flood stage, снимите блокировку отдельно.","tags":["disk","watermark","flood-stage","allocation","capacity","danger"]},{"code":"circuit_breaking_exception","title":"CircuitBreakingException[[parent]]: данные слишком велики, данные для [<...>] составили бы [...], что превышает лимит [...]","severity":"danger","meaning":"Запрос отклонён, поскольку превысил бы лимит circuit breaker по памяти (heap). Elasticsearch заранее останавливает запрос, чтобы узел не аварийно завершился с OutOfMemoryError. Breaker [parent] контролирует общее использование памяти; breaker [fielddata] — память fielddata для текстовых полей.","cause":"Недостаточный JVM heap, очень большие/сложные агрегации, большие наборы результатов, крупные bulk-запросы, fielddata, загруженная для текстовых полей, или множество тяжёлых параллельных запросов, заполняющих heap. Обычно это симптом нехватки heap.","fix":"Уменьшите запросы: сократите размер агрегаций, размер пакетов bulk и возвращаемые поля. Агрегируйте по подполю keyword (.keyword), а не по fielddata текстовых полей. Увеличьте JVM heap (~50% RAM сервера, не более ~31GB). Отслеживайте нагрузку на heap: GET _cat/nodes?v&h=name,heap.percent. Устраняйте первопричину (память/запрос), а не повышайте временно лимиты breaker. Проверьте, какой breaker сработал, с помощью GET _nodes/stats/breaker.","tags":["circuit-breaker","data-too-large","heap","memory","fielddata","oom","danger"]},{"code":"mapper_parsing_exception","title":"mapper_parsing_exception: не удалось разобрать поле [...] типа [...]","severity":"caution","meaning":"При индексации документа значение, отправленное для поля, не соответствовало типу, заданному для этого поля в mapping, и не могло быть разобрано. Например, текст, отправленный в поле типа integer, или недопустимый формат даты в поле типа date.","cause":"Несоответствие типа поля (строка для числового поля, неверный формат для поля даты), дата, не соответствующая ожидаемому формату, некорректный/неполный JSON, поля с ошибочно определённым динамическим типом или скалярное значение там, где ожидается объект.","fix":"Проверьте имя поля и ожидаемый тип в сообщении об ошибке: GET /index-name/_mapping. Приведите значение в документе в соответствие с типом поля или переопределите mapping с правильным типом. Для полей даты согласуйте параметр format с форматом документа. Тип существующего поля изменить нельзя; при необходимости создайте новый индекс и перенесите данные с помощью _reindex. Проверяйте данные на уровне приложения.","tags":["mapping","parse","mapper","field-type","indexing","caution"]},{"code":"strict_dynamic_mapping_exception","title":"strict_dynamic_mapping_exception: mapping задан как strict, динамическое добавление [...] внутри [...] не допускается","severity":"caution","meaning":"Поскольку mapping индекса или объекта задан как \"dynamic\": \"strict\", документ с новым полем, не определённым в mapping, был отклонён. Строгий режим не позволяет молча добавлять поля вне схемы.","cause":"В документе отправлено поле, отсутствующее в mapping (опечатка, новое поле, неожиданное поле), при mapping с dynamic:strict. Обычно это намеренная защита для строгого контроля схемы.","fix":"Исправьте или удалите лишнее/ошибочно названное поле в документе. Если поле действительно нужно, сначала добавьте его в mapping: PUT /index-name/_mapping {\"properties\": {\"new_field\": {\"type\": \"...\"}}}. Если нужно, чтобы новые поля добавлялись автоматически, задайте dynamic значение \"true\", или \"false\", если они должны храниться, но не индексироваться. Используйте строгий режим осознанно, чтобы сохранять дисциплину схемы.","tags":["mapping","strict","dynamic","schema","field","caution"]},{"code":"illegal_argument_exception_fielddata","title":"illegal_argument_exception: fielddata по умолчанию отключена для текстовых полей. Задайте fielddata=true для [your_field], чтобы загрузить fielddata в память","severity":"caution","meaning":"Предпринята попытка агрегации, сортировки или скрипта по анализируемому текстовому полю. Для текстовых полей эти операции по умолчанию отключены, поскольку требуют затратной по памяти fielddata.","cause":"Агрегация/сортировка применена непосредственно к текстовому полю. В Elasticsearch text используется для полнотекстового поиска, а keyword — для агрегации/сортировки по точным значениям; их смешение приводит к этой ошибке.","fix":"Для агрегации/сортировки используйте подполе keyword (например, \"my_field.keyword\"). Если поле только текстовое, добавьте в mapping подполе keyword (multi-field) и выполните _reindex. Включение fielddata для текстового поля (fielddata=true) возможно, но не рекомендуется из-за большого потребления памяти и риска срабатывания circuit breaker; используйте его только при крайней необходимости.","tags":["fielddata","text","keyword","aggregation","sorting","mapping","caution"]},{"code":"search_phase_execution_exception","title":"search_phase_execution_exception: сбой на всех шардах / не удалось выполнить фазу [query]","severity":"danger","meaning":"Поисковый запрос завершился ошибкой на одном или всех требуемых шардах. Это общая ошибка поиска, оборачивающая фактическую причину; раздел «caused by» указывает реальную проблему (ошибка разбора, breaker, неподходящий тип поля, недоступный шард и т. д.).","cause":"Недопустимый синтаксис запроса, агрегация по полю неверного типа, too_many_clauses, срабатывание circuit breaker, неназначенный/недоступный шард, тайм-аут запроса или сбой узла. Первопричина — во внутреннем исключении ответа.","fix":"Найдите фактическое исключение в разделах ответа «caused by» / «failed_shards» и исправьте соответственно (исправьте запрос, используйте поле keyword, настройки breaker/heap, размещение шардов). Проверьте состояние кластера: GET _cluster/health и GET _cat/shards?v. Если частичные результаты допустимы и часть шардов завершилась ошибкой, проверьте поведение allow_partial_search_results.","tags":["search","shard","search","all-shards-failed","query","danger"]},{"code":"too_many_clauses","title":"too_many_clauses: maxClauseCount равен 1024 (в запросе слишком много условий)","severity":"caution","meaning":"Запрос превысил максимально допустимое число предложений Lucene. В ES 8.x этот лимит не фиксирован; он рассчитывается автоматически исходя из размера heap узла и числа поисковых потоков (на узлах с малым heap может опускаться до 1024), а сообщение об ошибке показывает текущее значение. Его вызывают очень большие запросы bool и запросы, разворачивающиеся во множество терминов.","cause":"Программно сгенерированные запросы bool с тысячами предложений should/must; запросы wildcard/prefix/fuzzy/query_string, разворачивающиеся во множество терминов; глубоко вложенные структуры bool. Примечание: запрос terms (список значений по одному полю) не подпадает под этот лимит; для него ограничение — index.max_terms_count (по умолчанию 65,536).","fix":"Упростите запрос: преобразуйте множество предложений term по одному полю в один запрос terms (или terms lookup для больших списков). Используйте более специфичные типы запросов или поля index_prefixes/keyword вместо wildcard/prefix. В ES 8.x параметр indices.query.bool.max_clause_count устарел и игнорируется; повысить лимит можно, увеличив heap узла. Пересмотрите логику генерации запросов.","tags":["too-many-clauses","max-clause-count","bool","terms","query","caution"]},{"code":"version_conflict_engine_exception","title":"version_conflict_engine_exception: [_doc][id]: конфликт версий, текущая версия [x] отличается от указанной [y] (HTTP 409)","severity":"caution","meaning":"Запрос на обновление/удаление документа отклонён оптимистичным управлением параллелизмом. Версия документа (или _seq_no/_primary_term) не совпадает с указанной в запросе; то есть кто-то изменил документ после того, как вы его прочитали. Возвращается HTTP 409.","cause":"Параллельные обновления одного и того же документа, попытка записи с устаревшей версией/seq_no, попытка создать (op_type=create) уже существующий документ или состояние гонки при _bulk.","fix":"Перечитайте документ и повторите запрос с текущими _seq_no и _primary_term (или version) (цикл чтение-изменение-запись). Для автоматических повторов используйте параметр retry_on_conflict в запросе _update; в _update_by_query/_delete_by_query передавайте conflicts=proceed, чтобы пропускать конфликтующие документы и продолжать. Если намерение — только создание, конфликт нормален; перехватывайте 409 в приложении и обрабатывайте соответствующим образом (повтор или пропуск).","tags":["version-conflict","409","concurrency","optimistic","seq-no","caution"]},{"code":"document_missing_exception","title":"document_missing_exception: [_doc][id]: документ отсутствует","severity":"caution","meaning":"Обновляемый документ (_update) не найден в индексе. Обновление требует существующего документа; если документа нет, возникает это исключение.","cause":"Документ был удалён ранее, никогда не создавался, использован неверный _id, запрос ушёл в неверный индекс или к документу, записанному с пользовательской маршрутизацией (routing), обратились без routing. (GET и _update работают в реальном времени; задержка refresh не вызывает эту ошибку.)","fix":"Убедитесь, что _id и целевой индекс указаны верно: GET /index-name/_doc/id. Если нужно поведение «обновить, если существует / создать, если отсутствует», используйте upsert (или doc_as_upsert:true) в запросе _update. Создавайте документ напрямую через index (PUT). В приложении перехватывайте это исключение и переходите к логике upsert.","tags":["document-missing","update","upsert","document","not-found","caution"]},{"code":"index_not_found_exception","title":"index_not_found_exception: индекс [index_name] не существует (HTTP 404)","severity":"caution","meaning":"Запрошенный индекс (или alias) не существует. Операция отклонена с HTTP 404, поскольку индекс с указанным именем не найден.","cause":"Индекс никогда не создавался, был удалён, в имени индекса/alias опечатка, клиент подключён к неверному окружению (кластеру) или предпринята запись в несуществующий индекс при отключённом action.auto_create_index.","fix":"Выведите список существующих индексов: GET _cat/indices?v. Проверьте имя и alias: GET _alias. Создайте индекс: PUT /index-name (с нужными mappings/settings) или настройте автоматическое создание через index template. Если автоматическое создание желательно, проверьте настройку action.auto_create_index. Убедитесь, что вы не подключены к неверному кластеру.","tags":["index-not-found","404","index","alias","not-found","caution"]},{"code":"resource_already_exists_exception","title":"resource_already_exists_exception: индекс [index_name/uuid] уже существует (HTTP 400)","severity":"safe","meaning":"Создаваемый индекс (или ресурс) уже существует. Запрос на создание отклонён, поскольку индекс с таким именем существует. В старых версиях также встречается как index_already_exists_exception.","cause":"Попытка создать один и тот же индекс дважды, неидемпотентные скрипты развёртывания, параллельные запросы на создание или попытка заново создать то же имя с другими настройками.","fix":"Если индекс уже существует, создавать его не нужно; продолжайте. Если его необходимо пересоздать, сначала удалите его (DELETE /index-name), затем создайте (осторожно: потеря данных). Делайте скрипты развёртывания идемпотентными: сначала проверяйте существование через GET _cat/indices. Предотвращайте конфликты, используя index templates или ILM/rollover для управления схемой.","tags":["already-exists","resource-already-exists","index","create","idempotent","safe"]},{"code":"cluster_health_red","title":"Состояние кластера: RED / неназначенные шарды","severity":"danger","meaning":"Состояние кластера RED означает, что хотя бы один primary-шард не назначен и недоступен. Это критическое состояние, при котором часть данных невозможно читать/записывать, с риском частичной потери данных. (YELLOW означает, что не назначены только реплики; primary-шарды целы.)","cause":"Узел аварийно завершился/покинул кластер, и primary-шарды на нём остались без копии, размещение заблокировано disk watermark, повреждённый шард, ошибка при восстановлении из snapshot или настройки размещения, препятствующие назначению шардов.","fix":"Выясните, почему шарды не назначены: GET _cluster/allocation/explain или GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason. Верните отказавшие узлы; если проблема в диске, освободите место (watermark). Если размещение было временно заблокировано, запустите повторное размещение: POST _cluster/reroute?retry_failed=true. Если primary полностью утрачен, восстановите данные из snapshot. В состоянии RED в первую очередь сосредоточьтесь на восстановлении primary-шардов.","tags":["cluster-health","red","unassigned","shard","primary","danger"]},{"code":"no_shard_available_action_exception","title":"no_shard_available_action_exception: нет доступного шарда для [...]","severity":"danger","meaning":"Не найдена активная копия шарда, способная обслужить запрос. Запрос невозможно выполнить, поскольку соответствующий шард не назначен, восстанавливается или узел, на котором он размещён, недоступен.","cause":"Сбой узла/сетевое разделение, неназначенные шарды, идущее восстановление шарда, блокировка размещения из-за disk watermark или кластер в состоянии RED/YELLOW.","fix":"Проверьте состояние кластера и шардов: GET _cluster/health и GET _cat/shards?v. Выясните, почему шарды не назначены: GET _cluster/allocation/explain. Верните отказавшие узлы, устраните проблему с диском и дождитесь завершения восстановления: GET _cat/recovery?v&active_only=true. При необходимости повторите размещение с помощью 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: выполнение ... на ... отклонено (ёмкость очереди ...) (HTTP 429)","severity":"danger","meaning":"Операция отклонена, поскольку очередь соответствующего пула потоков (search, write/bulk и т. д.) заполнена. При перегрузке Elasticsearch защищает себя, отклоняя запросы (backpressure), и возвращает HTTP 429 (Too Many Requests).","cause":"Очень большой объём параллельных запросов, очень крупные или очень частые bulk-запросы, высокая нагрузка агрегаций/поиска, недостаточные ресурсы узлов/CPU или слишком маленькая для нагрузки очередь пула потоков.","fix":"Повторяйте запросы на стороне клиента с экспоненциальной задержкой; 429 обычно временная ошибка. Уменьшите размер пакетов bulk и скорость отправки, снизьте параллельность. Отслеживайте отклонённые операции: GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected. При устойчивой нагрузке добавьте узлы/ресурсы (горизонтальное масштабирование). Увеличение размера очереди — последнее средство; первопричина — ёмкость.","tags":["rejected-execution","429","thread-pool","queue","bulk","backpressure","danger"]},{"code":"master_not_discovered_exception","title":"master_not_discovered_exception / MasterNotDiscoveredException","severity":"danger","meaning":"Кластер не смог выбрать/найти активный узел master. Без master невозможны размещение шардов, изменения mapping и обновления состояния кластера; кластер перестаёт обрабатывать операции.","cause":"Большинство (кворум) узлов, способных быть master, недоступно, сетевое разделение (защита от split brain), неверная конфигурация discovery.seed_hosts / cluster.initial_master_nodes, отсутствие bootstrap при новой установке или слишком мало узлов, способных быть master.","fix":"Убедитесь, что узлы, способные быть master, запущены и видят друг друга: GET _cat/nodes?v и GET _cluster/state. Проверьте настройки discovery.seed_hosts и cluster.initial_master_nodes (при первоначальной настройке). Проверьте сеть/межсетевой экран и связность между узлами (transport 9300). Держите запущенными достаточно узлов, способных быть master (желательно 3). Изучите журналы на предмет сообщений о выборах master.","tags":["master","discovery","quorum","split-brain","cluster","danger"]},{"code":"security_exception","title":"security_exception: не удаётся выполнить аутентификацию / действие [...] не разрешено пользователю [...] (HTTP 401/403)","severity":"danger","meaning":"Не пройдена аутентификация (401 Unauthorized) или авторизация (403 Forbidden). 401 указывает на отсутствующие/неверные учётные данные; 403 — на то, что пользователь аутентифицирован, но не имеет необходимой роли/привилегии для этого действия/индекса.","cause":"Неверные имя пользователя/пароль или API key, отсутствующий заголовок Authorization, истёкший/недействительный токен, отсутствие у пользователя соответствующей привилегии на индекс или кластер либо отсутствующее/неверное назначение роли.","fix":"Для 401: передайте правильные учётные данные/API key и проверьте заголовок Authorization. Для 403: проверьте роли и привилегии пользователя (GET _security/user/app_user, GET _security/role/app_role) и добавьте необходимые привилегии на индексы/кластер. Следуйте принципу наименьших привилегий. Если используете API key, проверьте срок его действия и разрешения.","tags":["security","401","403","authentication","authorization","security","danger"]},{"code":"validation_exception","title":"validation_exception / illegal_argument_exception: проверка не пройдена: ...","severity":"caution","meaning":"Параметры или настройки запроса не прошли проверку. Elasticsearch отклонил запрос с недопустимыми или несогласованными значениями, не выполняя его (например, недопустимое число шардов, несовместимое сочетание настроек, отсутствующее обязательное поле).","cause":"Недопустимая настройка индекса (например, отрицательное/нулевое number_of_shards), попытка изменить неизменяемую настройку существующего индекса, несовместимое сочетание параметров, отсутствующее обязательное поле или значение вне допустимого диапазона.","fix":"Прочитайте подробности проверки в сообщении об ошибке; там ясно указано, какой параметр недопустим и почему. Приведите значение к допустимому диапазону/формату. Задавайте неизменяемые (static) настройки при создании нового индекса, а не для существующего (при необходимости — _reindex). Передавайте обязательные поля, которые ожидает API. Сверьтесь с документированными допустимыми значениями настройки.","tags":["validation","validation","settings","illegal-argument","invalid","caution"]},{"code":"task_cancelled_exception","title":"task_cancelled_exception: задача отменена / TaskCancelledException","severity":"caution","meaning":"Выполнявшаяся задача (search, _reindex, _update_by_query и т. д.) была отменена. Отмена может произойти по запросу пользователя к _tasks/_cancel, при закрытии клиентом HTTP-соединения (тайм-аут/прерывание на клиенте; Elasticsearch автоматически отменяет связанный поиск), при отмене родительской задачи или истечении асинхронного поиска.","cause":"Пользователь отменил задачу через POST _tasks/<task_id>/_cancel (X-Opaque-Id только идентифицирует задачу и помогает её найти, но не отменяет её), тайм-аут клиента разорвал соединение, длительные затратные запросы или адаптивное управление ресурсами остановило тяжёлую задачу.","fix":"Если задача была отменена неожиданно, проверьте тайм-ауты клиента и настройки подключения; оптимизируйте тяжёлый запрос (меньше данных, более точные фильтры). Для длительных операций запускайте _reindex/_update_by_query асинхронно (wait_for_completion=false) и отслеживайте через GET _tasks. Просмотр активных задач: GET _tasks?detailed=true. При необходимости разделите затратные запросы или используйте 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":"Ошибка на уровне рекурсивного SQL","severity":"danger","meaning":"Произошла ошибка во время рекурсивного SQL, который Oracle выполняет внутренне в фоне. Это ошибка-обёртка (внешняя), скрывающая фактическую ошибку; реальная причина — во втором коде ошибки, указанном непосредственно под ней.","cause":"Обращение к словарю данных, сбой DDL-триггера, заполненное табличное пространство SYSTEM, недостаток временного пространства, повреждённый объект словаря данных или сбой LOGON-триггера, выполняемого при входе в сессию.","fix":"Прочитайте вторую строку, следующую за ORA-00604 в стеке ошибок; это реальная причина — устраните её. Например, если видите ORA-01653, расширьте табличное пространство. Если подозревается DDL/LOGON-триггер, временно отключите его: ALTER TRIGGER trigger_name DISABLE; Проверьте использование табличных пространств SYSTEM и TEMP через DBA_FREE_SPACE.","tags":["recursive","recursion","dictionary","data dictionary","trigger","wrapper"]},{"code":"ORA-01438","title":"Значение превышает точность, допустимую для этого столбца","severity":"caution","meaning":"В столбец NUMBER записано число, превышающее заданные пределы точности и масштаба. Например, значение с 3-значной целой частью не помещается в столбец NUMBER(4,2).","cause":"Определение NUMBER(p,s) столбца слишком узкое для данных; результат вычисления больше ожидаемого; десятичный масштаб сокращает число разрядов для целой части (NUMBER(5,3) допускает только 2 целых разряда).","fix":"Расширьте ёмкость столбца в соответствии с реальным диапазоном данных: ALTER TABLE my_table MODIFY (my_column NUMBER(12,2)); Округляйте значение в приложении или приводите его к масштабу с помощью ROUND/TRUNC. Чтобы найти переполняющую строку, сужайте запрос при тестировании и добавьте проверку входных данных.","tags":["number","precision","precision","scale","scale","overflow","numeric"]},{"code":"ORA-01400","title":"Невозможно вставить NULL в (столбец)","severity":"caution","meaning":"В столбец с ограничением NOT NULL вставлено или присвоено значение NULL. Обязательное поле не может оставаться пустым.","cause":"Не передано значение для обязательного столбца в INSERT, UPDATE присваивает столбцу NULL, для столбца не определён DEFAULT или пустое значение пришло из подзапроса/приложения.","fix":"Передайте допустимое значение для столбца, указанного в сообщении об ошибке. Определите для столбца разумное значение по умолчанию: ALTER TABLE my_table MODIFY (my_column DEFAULT 0); (DEFAULT применяется, только если столбец полностью опущен в INSERT; для явно переданных значений NULL используйте DEFAULT ON NULL из 12c+: ALTER TABLE my_table MODIFY (my_column DEFAULT ON NULL 0);) Если правило на самом деле не обязательное, снимите ограничение: ALTER TABLE my_table MODIFY (my_column NULL); Проверяйте пустые значения в приложении перед отправкой.","tags":["not null","null","empty","insert","constraint","constraint","mandatory"]},{"code":"ORA-02291","title":"Нарушено ограничение целостности — родительский ключ не найден","severity":"danger","meaning":"Нарушено ограничение внешнего ключа. Не найдена родительская строка, соответствующая значению, вставленному в дочернюю таблицу; то есть запись, на которую выполняется ссылка, не существует.","cause":"INSERT/UPDATE со ссылкой на несуществующую родительскую запись, строка родительской таблицы ещё не вставлена, неверный порядок загрузки (дочерние раньше родительских) или несовпадение из-за различий в регистре или пробелах.","fix":"Сначала вставьте соответствующую запись в родительскую таблицу, затем в дочернюю. Убедитесь, что ссылка существует: SELECT * FROM parent_table WHERE key_col = value; При массовой загрузке обрабатывайте таблицы в правильном порядке (сначала родительские). Чтобы найти нарушающую строку, сравните значение в дочерней таблице с родительской с помощью NOT IN / anti-join.","tags":["fk","foreign key","foreign key","constraint","constraint","parent","reference"]},{"code":"ORA-02292","title":"Нарушено ограничение целостности — найдена дочерняя запись","severity":"danger","meaning":"Родительская строка удалена или её ключ изменён, но ограничение внешнего ключа этого не допустило, поскольку существуют зависимые дочерние записи.","cause":"Попытка выполнить DELETE родительской строки, у которой есть записи в дочерней таблице; FK определён без ON DELETE CASCADE; зависимые записи не были предварительно удалены.","fix":"Сначала удалите зависимые дочерние записи или перенесите их к другой родительской записи, затем удалите родительскую строку. Если бизнес-правило это допускает, пересоздайте внешний ключ с 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; Чтобы найти зависимые строки, выполните запрос к дочерней таблице по родительскому ключу.","tags":["fk","foreign key","foreign key","child","child record","delete","cascade"]},{"code":"ORA-00979","title":"Выражение не входит в GROUP BY (not a GROUP BY expression)","severity":"caution","meaning":"В списке SELECT вместе с агрегатной функцией (SUM, COUNT и т. д.) использован неагрегированный столбец, но он не добавлен в предложение GROUP BY. Oracle не знает, как группировать этот столбец.","cause":"Столбец есть в SELECT, но отсутствует в GROUP BY; выражения вне агрегатных функций отсутствуют в списке GROUP BY; несогласованность в HAVING или выражениях выборки.","fix":"Добавьте все неагрегированные столбцы в список GROUP BY: SELECT department, COUNT(*) FROM employee GROUP BY department; В качестве альтернативы также оберните этот столбец в агрегатную функцию (MAX, MIN) или перестройте группировку с помощью аналитической функции (OVER()).","tags":["group by","grouping","aggregate","aggregation","sum","count","query"]},{"code":"ORA-00918","title":"Столбец определён неоднозначно","severity":"caution","meaning":"Столбец с одним и тем же именем есть в нескольких таблицах запроса, и поскольку он используется без префикса таблицы/псевдонима, Oracle не может определить, к какой таблице он относится.","cause":"В обеих соединяемых таблицах есть столбец с одинаковым именем (например, ID в обеих); столбец используется без квалификации (без префикса) в SELECT, WHERE или ORDER BY.","fix":"Квалифицируйте каждый неоднозначный столбец именем таблицы или псевдонимом: SELECT a.id, b.id FROM tableA a JOIN tableB b ON a.id = b.a_id; Давайте таблицам короткие псевдонимы и указывайте префикс у всех столбцов для удобства чтения и ясности.","tags":["ambiguous","ambiguous column","column","column","join","alias","query"]},{"code":"ORA-06550","title":"Строка/столбец PLS-...: ошибка компиляции PL/SQL","severity":"caution","meaning":"При компиляции блока PL/SQL, процедуры или анонимного блока обнаружена синтаксическая или семантическая ошибка. Код завершился ошибкой на этапе компиляции, не дойдя до выполнения.","cause":"Опечатки, ссылка на необъявленную переменную или объект, вызов с неверным числом/типом параметров, отсутствующая точка с запятой, вызов несуществующей процедуры/пакета или отсутствующая привилегия.","fix":"Перейдите к номеру строки и столбца из сообщения об ошибке; прочитайте подкод PLS-XXXX. Чтобы увидеть все ошибки компиляции: SHOW ERRORS; или SELECT * FROM user_errors WHERE name = 'OBJECT_NAME'; Проверьте имена объектов, сигнатуры параметров и необходимые GRANT, затем перекомпилируйте.","tags":["plsql","compilation","compile","pls","syntax","procedure","code"]},{"code":"ORA-04068","title":"Существующее состояние пакетов было сброшено","severity":"caution","meaning":"Пока выполняющаяся сессия использовала состояние пакета, пакет был перекомпилирован, и это состояние стало недействительным. Экземпляр пакета, удерживаемый сессией, больше не действителен.","cause":"Тело или спецификация пакета перекомпилированы через ALTER/CREATE OR REPLACE, пока зависящие от них сессии были активны; обычно происходит при развёртывании приложения на работающей системе. Наблюдается вместе с ORA-04061/04065.","fix":"Повторный вызов (retry) обычно решает проблему; при второй попытке пакет загружается в новом виде. Перехватывайте ORA-04068 в приложении и автоматически повторяйте вызов. Применяйте изменения пакетов в окна обслуживания с низкой активностью пользователей. Проектирование пакетов без состояния снижает остроту проблемы.","tags":["package","package","state","package state","recompile","compile","retry"]},{"code":"ORA-01427","title":"Однострочный подзапрос возвращает более одной строки","severity":"caution","meaning":"Подзапрос, от которого ожидалась одна строка, вернул больше одной строки. Например, подзапрос в правой части сравнения на равенство (=) выдал несколько значений.","cause":"Подзапрос возвращает несколько строк там, где ожидается = или присваивание; недостаточно суженное условие WHERE; неуникальное совпадение в корреляции; скалярный подзапрос в списке SELECT возвращает несколько строк.","fix":"Сузьте подзапрос, чтобы он возвращал одну строку (дополнительный WHERE, уникальный ключ). Если действительно ожидается несколько значений, используйте IN вместо =: WHERE id IN (SELECT id FROM ...). Либо добавьте в подзапрос агрегат (MAX, MIN) или сократите его до одной строки с помощью ROWNUM = 1. Сначала выполните подзапрос отдельно и проверьте, сколько строк он возвращает.","tags":["subquery","subquery","single row","single row","in","scalar","query"]},{"code":"ORA-12899","title":"Значение слишком велико для столбца","severity":"caution","meaning":"В символьный столбец/столбец данных записано значение длиннее заданной максимальной длины. Например, 15-символьный текст не помещается в столбец VARCHAR2(10).","cause":"Ширины столбца недостаточно для данных; в многобайтовом наборе символов (UTF8) число символов превышает лимит в байтах (семантика BYTE); другой набор символов при миграции данных; результат конкатенации становится длиннее.","fix":"Расширьте столбец: ALTER TABLE my_table MODIFY (my_column VARCHAR2(100)); Для многобайтового содержимого используйте семантику CHAR: my_column VARCHAR2(100 CHAR). Если длинные значения нужно сократить, усекайте их с помощью SUBSTR. Сообщение об ошибке содержит фактическую и допустимую длину; найдите переполняющую запись с помощью LENGTH/LENGTHB.","tags":["varchar2","length","length","too large","column","column","charset"]},{"code":"ORA-30926","title":"Не удаётся получить стабильный набор строк в исходных таблицах","severity":"danger","meaning":"В операторе MERGE (или обновляемом соединении) сторона источника выдала более одной совпадающей строки для одной и той же целевой строки. Oracle остановил операцию, поскольку не может решить, каким значением источника выполнять обновление.","cause":"Источник MERGE ... USING содержит дублирующиеся совпадения по условию ON; запрос-источник не уникален по целевому ключу; несколько строк источника сопоставляются одной целевой строке.","fix":"Сделайте запрос-источник уникальным по целевому ключу. Найдите дублирующиеся совпадения: SELECT key_col, COUNT(*) FROM (source_query) GROUP BY key_col HAVING COUNT(*) > 1; Устраните дубликаты в источнике с помощью DISTINCT или агрегатной/аналитической функции (ROW_NUMBER), затем повторите MERGE. Убедитесь, что условие ON достаточно селективно.","tags":["merge","merge","duplicate","duplicate rows","source","source","stable"]},{"code":"ORA-08103","title":"Объект больше не существует","severity":"danger","meaning":"Объект (таблица, индекс, LOB-сегмент), к которому обращается выполняющийся запрос, больше не существует или изменилась его физическая структура. Обычно во время выполнения запроса над объектом был выполнен DDL/TRUNCATE.","cause":"Тот же объект был подвергнут TRUNCATE/DROP/SHRINK, пока запрос был открыт; перемещение (MOVE) или обслуживание секций; повреждённый индекс/сегмент; исчезновение базовой структуры при обращении к LOB-сегменту; конфликт с параллельным DDL.","fix":"Убедитесь, что объект по-прежнему существует и действителен: SELECT status FROM user_objects WHERE object_name = 'NAME'; Перестройте соответствующий индекс/объект (REBUILD) или повторите запрос. Избегайте TRUNCATE/DROP для объектов, пока их используют длительные запросы. Если подозревается повреждение, проверьте с помощью ANALYZE ... VALIDATE STRUCTURE.","tags":["object","object","truncate","drop","ddl","corruption","segment"]},{"code":"ORA-01843","title":"Недопустимый месяц","severity":"caution","meaning":"При преобразовании текста в DATE часть, соответствующую месяцу, не удалось интерпретировать как допустимый месяц. Обычно формат даты не соответствует формату данных.","cause":"В TO_DATE не передана маска формата, и NLS_DATE_FORMAT сессии не соответствует данным; перепутаны день и месяц (путаница DD/MM и MM/DD); различие языка/локали (в названиях месяцев); повреждённый или неожиданный текст даты.","fix":"Всегда передавайте явный формат в вызовах TO_DATE: TO_DATE('2026-07-04', 'YYYY-MM-DD'). Для текста с названиями месяцев указывайте язык: TO_DATE(str, 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH'). При необходимости задайте формат сессии: ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'. Проверяйте недопустимый текст дат перед загрузкой.","tags":["date","date","to_date","nls","format","month","month"]},{"code":"ORA-14400","title":"Вставленный ключ секционирования не соответствует ни одной секции","severity":"danger","meaning":"Значение ключа секционирования во вставляемой строке не соответствует ни одной секции, определённой для таблицы. Нет ни диапазонной, ни списочной секции, охватывающей это значение.","cause":"При секционировании RANGE вставлена дата/значение выше максимального диапазона, и секции MAXVALUE нет; при секционировании LIST — новое неопределённое значение; новая секция не была заранее создана на границе месяца/года.","fix":"Добавьте недостающую секцию: ALTER TABLE my_table ADD PARTITION p_2026_08 VALUES LESS THAN (TO_DATE('2026-09-01','YYYY-MM-DD')); Для RANGE добавьте секцию MAXVALUE в качестве страховки. Для LIST определите секцию DEFAULT, чтобы перехватывать неожиданные значения. Используйте автоматическое секционирование (INTERVAL partitioning), чтобы новые секции создавались автоматически.","tags":["partition","partition","partition key","range","list","maxvalue","insert"]},{"code":"ORA-01536","title":"Превышена квота пространства для табличного пространства","severity":"danger","meaning":"Квота пользователя на указанное табличное пространство исчерпана. Даже если в табличном пространстве есть свободное место, новое пространство выделить невозможно, поскольку превышен лимит, выданный пользователю.","cause":"У пользователя ограниченная квота (не UNLIMITED); квота заполнилась по мере роста данных; чрезмерная загрузка в таблицу/индекс; запись в неверное табличное пространство; сегменты растут быстрее, чем ожидалось.","fix":"Увеличьте квоту пользователя: ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; или задайте конкретное значение: QUOTA 500M ON users. Просмотр текущего использования: SELECT * FROM dba_ts_quotas WHERE username = 'APP_USER'. Архивируйте/удаляйте излишне большие сегменты или перенесите объекты в подходящее табличное пространство.","tags":["quota","quota","tablespace","space","space","user","user"]},{"code":"direct path read","title":"Ожидание: сессия читает блоки напрямую в PGA в обход buffer cache","severity":"caution","meaning":"Сессия проводит время в этом событии ожидания, читая блоки напрямую в память PGA, а не через buffer cache базы данных. Обычно связано с крупными полными сканированиями таблиц и параллельными запросами.","cause":"Чтение множества блоков с диска из-за полных сканирований больших таблиц, параллельные запросы, операции sort/hash, сбрасываемые во временное табличное пространство, решение о serial direct path read для последовательных полных сканирований и запросы, которые не могут использовать индекс.","fix":"Определите SQL с интенсивными полными сканированиями с помощью AWR/ASH и добавьте подходящие индексы. Сократите лишние сканирования больших таблиц за счёт настройки запросов. Исследуйте задержку хранилища (I/O) и при необходимости перейдите на более быстрые диски. Пересмотрите степень параллелизма. Для операций, сбрасываемых в TEMP, оцените размер PGA_AGGREGATE_TARGET.","tags":["wait","wait","direct path read","full scan","full table scan","io","pga"]},{"code":"latch: cache buffers chains","title":"Ожидание: конкуренция за latch cache buffers chains","severity":"danger","meaning":"Наблюдается конкуренция за latch cache buffers chains (CBC), защищающий блоки в buffer cache. Это ожидание растёт, когда множество сессий пытается обратиться к одним и тем же часто запрашиваемым (горячим) блокам.","cause":"Часто читаемые горячие блоки; неэффективный SQL, порождающий большое число логических чтений; повторные обращения к одним и тем же корневым блокам индексов в соединениях nested loop; неравномерно распределённый интенсивный параллельный доступ; маленькие, но интенсивно читаемые таблицы.","fix":"Найдите SQL с наибольшим числом логических чтений (V$SQL, ASH) и сократите обращения к буферам, улучшив индексы/план доступа. Определите горячие блоки (X$BH / связанные представления). Рассмотрите замену лишних nested loops на hash joins. Для маленьких, интенсивно читаемых справочных таблиц распределите паттерн доступа или кэшируйте их.","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":"Функция (или оператор), вызванная в запросе, не найдена с таким именем и такими типами аргументов. PostgreSQL разрешает функции и по имени, и по типам аргументов; если сигнатура не совпадает, возникает эта ошибка.","cause":"Опечатка в имени функции; типы аргументов, не соответствующие определённой сигнатуре (например, число вместо текста); отсутствие явного приведения типа; схема функции отсутствует в search_path; не установлено необходимое расширение.","fix":"Убедитесь, что функция существует, и проверьте её сигнатуру: \\df function_name. Приводите аргументы явно: SELECT fn(value::text). Добавьте префикс схемы или задайте search_path. Если требуется расширение, установите его: CREATE EXTENSION extension_name; При ошибках операторов приведите типы с обеих сторон к соответствию.","tags":["function","function not found","undefined","operator","cast","type conversion","42883"]},{"code":"42804","title":"datatype_mismatch","severity":"caution","meaning":"Ожидаемый и переданный типы данных не совпадают. Например, присвоение столбцу значения неверного типа, ветви CASE, возвращающие разные типы, или ветви UNION с несогласованными типами.","cause":"Присваивание, несовместимое с типом столбца; ветви CASE/COALESCE, дающие разные типы; соответствующие столбцы в UNION разных типов; значение в RETURN, не соответствующее типу результата функции; не boolean там, где ожидается boolean.","fix":"Приведите выражение к правильному типу: value::integer или CAST(value AS numeric). Сделайте так, чтобы все ветви CASE возвращали один и тот же тип. В UNION согласуйте типы соответствующих столбцов с обеих сторон. Убедитесь, что условие WHERE даёт boolean.","tags":["type","data type","mismatch","type mismatch","cast","union","42804"]},{"code":"22P02","title":"invalid_text_representation","severity":"caution","meaning":"При преобразовании текстовое значение не было допустимым представлением для целевого типа данных. Например, попытка преобразовать текст 'abc' в integer вызывает эту ошибку.","cause":"Нечисловой текст передан в числовой столбец/параметр; пустая строка ('') преобразуется в число/дату; недопустимый текст для UUID или enum; некорректный JSON; входные данные не в ожидаемом формате.","fix":"Убедитесь, что преобразуемое значение действительно допустимо. Проверяйте входные данные в приложении до преобразования типа. Преобразуйте пустые строки в NULL: NULLIF(value, ''). Для безопасного преобразования в PostgreSQL 16+ заранее проверяйте значение с помощью pg_input_is_valid('abc', 'integer') и используйте pg_input_error_info() для подробностей об ошибке; в старых версиях рассмотрите проверку (regex). Выясните, какая именно строка некорректна.","tags":["cast","type conversion","invalid text","invalid input","integer","uuid","22P02"]},{"code":"22003","title":"numeric_value_out_of_range","severity":"caution","meaning":"Числовое значение выходит за допустимый для целевого типа диапазон. Например, запись значения больше 2 миллиардов в столбец integer или слишком большого числа в столбец numeric(5,2).","cause":"Превышение пределов smallint/integer/bigint; превышение точности/масштаба numeric(p,s); арифметическое переполнение; сумма/произведение, превышающие ёмкость типа; последовательность, достигшая верхней границы своего типа.","fix":"Измените тип столбца/параметра на более широкий (bigint вместо integer, увеличьте точность numeric): ALTER TABLE my_table ALTER COLUMN my_column TYPE bigint; Округляйте (ROUND) или проверяйте значение согласно бизнес-правилу. Если переполняется промежуточное вычисление, вычисляйте выражение как numeric. При переполнении последовательности расширьте тип.","tags":["numeric","number","out of range","overflow","numeric overflow","integer","22003"]},{"code":"23514","title":"check_violation","severity":"caution","meaning":"Нарушено ограничение CHECK. Вставленная или обновлённая строка не удовлетворяет логическому условию, определённому для таблицы (например, price > 0).","cause":"Данные, нарушающие бизнес-правило (отрицательная цена, недопустимый код статуса, нарушение диапазона дат); отсутствие проверки на стороне приложения; слишком строгое для данных ограничение; записи, нарушающие правило при массовой загрузке.","fix":"По имени ограничения в сообщении об ошибке определите, какое условие нарушено; просмотрите определение: \\d table_name. Найдите нарушающие строки запросом с инвертированным условием. Исправьте данные или, если правило действительно изменилось, обновите ограничение: 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":"Арифметическое выражение попыталось выполнить деление (или взятие остатка) на ноль. PostgreSQL не допускает деления на ноль и прерывает запрос.","cause":"Столбец/выражение делителя равно 0; нулевой знаменатель при вычислении отношения/процента; деление на COUNT или SUM, равные нулю; неожиданные нулевые значения в нефильтрованных данных.","fix":"Используйте NULLIF, чтобы возвращать NULL при нулевом знаменателе: numerator / NULLIF(denominator, 0). При желании превратите результат в значение по умолчанию с помощью COALESCE: COALESCE(numerator / NULLIF(denominator,0), 0). Либо используйте CASE WHEN denominator = 0 THEN NULL ELSE numerator/denominator END. Заранее отфильтруйте нулевые случаи в данных.","tags":["division","divide","zero","divide by zero","nullif","arithmetic","22012"]},{"code":"42601","title":"syntax_error","severity":"caution","meaning":"В SQL-операторе обнаружена синтаксическая ошибка. PostgreSQL не смог разобрать запрос и сообщает приблизительное место (position) ошибки.","cause":"Отсутствующая/лишняя запятая или скобка; зарезервированное ключевое слово (например, user, order), использованное как имя столбца без кавычек; отсутствующая точка с запятой; опечатка в команде; незакрытый строковый литерал; использование синтаксиса конкретной версии.","fix":"Используйте значение 'position' из сообщения об ошибке, чтобы найти проблему; изучите часть непосредственно перед ним. Заключайте зарезервированные слова в двойные кавычки: \"order\". Проверьте баланс скобок и кавычек. Тестируйте сложный запрос, разбивая его на части. Сверяйтесь с документацией вашей версии для правильного синтаксиса.","tags":["syntax","syntax error","parse","parser","keyword","position","42601"]},{"code":"42P07","title":"duplicate_table","severity":"caution","meaning":"Создаваемая таблица (или подобный объект) уже существует. CREATE завершился ошибкой, поскольку объект с таким именем уже есть.","cause":"Повторное создание существующей таблицы через CREATE TABLE; двойной запуск скрипта; повторное применение миграций; конфликт имени с одноимёнными представлением/последовательностью/индексом.","fix":"Используйте IF NOT EXISTS, чтобы пропустить создание, если объект уже существует: CREATE TABLE IF NOT EXISTS my_table (...). Если действительно нужно пересоздать его, сначала удалите: DROP TABLE IF EXISTS my_table; Делайте миграции безопасными для повторного запуска (идемпотентными). Проверьте существующий объект с помощью \\dt.","tags":["duplicate","duplicate object","table","relation","create","already exists","42P07"]},{"code":"42701","title":"duplicate_column","severity":"caution","meaning":"Одно и то же имя столбца использовано несколько раз в определении таблицы или операции ALTER, либо уже существующий столбец добавлен повторно.","cause":"Двукратное указание одного имени столбца в CREATE TABLE; добавление уже существующего столбца через ALTER TABLE ADD COLUMN; ошибка копирования-вставки; повторный запуск миграции.","fix":"Удалите или переименуйте дублирующееся имя столбца в определении таблицы. Сначала проверьте, чтобы не добавлять существующий столбец повторно: \\d table_name. Если версия поддерживает, добавляйте безопасно: ALTER TABLE my_table ADD COLUMN IF NOT EXISTS my_column type; Убедитесь, что миграции идемпотентны.","tags":["duplicate","duplicate column","column","column exists","alter","add column","42701"]},{"code":"3D000","title":"invalid_catalog_name","severity":"danger","meaning":"База данных, к которой вы пытаетесь подключиться или обратиться, не существует. База данных с указанным именем не найдена.","cause":"Опечатка в имени базы данных в строке подключения; база данных ещё не создана или удалена; чувствительность к регистру; подключение к неверному серверу; неверное имя в конфигурации окружения (env).","fix":"Выведите список существующих баз данных: psql -l или SELECT datname FROM pg_database. Исправьте имя базы данных в строке подключения. При необходимости создайте её: CREATE DATABASE database_name; Убедитесь, что подключаетесь к правильному серверу/порту. Проверьте имя БД в конфигурации приложения.","tags":["database","database not found","catalog","does not exist","missing","connection","3D000"]},{"code":"3F000","title":"invalid_schema_name","severity":"caution","meaning":"Указанная схема не существует или не найдена в search_path. Схемы с таким именем нет.","cause":"Опечатка в имени схемы; схема никогда не создавалась; search_path не включает схему; различие в регистре; неверный префикс при обращении к объекту с префиксом схемы.","fix":"Выведите список существующих схем: \\dn или SELECT schema_name FROM information_schema.schemata. Исправьте имя схемы. При необходимости создайте её: CREATE SCHEMA schema_name; Задайте search_path: SET search_path TO schema_name, public; Обращайтесь к объектам с правильным префиксом схемы.","tags":["schema","schema not found","search_path","does not exist","missing","invalid","3F000"]},{"code":"0A000","title":"feature_not_supported","severity":"caution","meaning":"Функциональность, которую вы пытаетесь использовать, не поддерживается в данном контексте или в этой версии PostgreSQL. Даже если запрошенная операция допустима, в текущей ситуации её применить нельзя.","cause":"Синтаксис/функциональность ещё недоступны в версии; ALTER, не поддерживаемый для определённого типа столбца/таблицы; операция, не поддерживаемая обёрткой сторонних данных (FDW); ограничения на сочетания логической репликации/ограничений; преобразования, не разрешённые для некоторых типов данных.","fix":"Прочитайте полное сообщение об ошибке; обычно в нём ясно сказано, что не поддерживается. Используйте альтернативный подход (например, новый столбец + копирование + переименование вместо прямого изменения типа столбца). Если функциональность появилась в более новой версии, рассмотрите обновление. Если вы упёрлись в ограничения FDW/расширения, сверьтесь с документацией.","tags":["not supported","unsupported","feature","unsupported feature","version","upgrade","0A000"]},{"code":"57P03","title":"cannot_connect_now","severity":"danger","meaning":"Сервер сейчас не может принимать подключения; обычно база данных ещё запускается или выполняет восстановление. Подключения можно будет открыть, когда система будет готова.","cause":"PostgreSQL только что запущен и ещё находится в фазе запуска/восстановления; восстановление после сбоя; восстановление из бэкапа; standby ещё не готов принимать запросы; идёт остановка.","fix":"Немного подождите и подключитесь снова; сервер начнёт принимать подключения после завершения запуска. Отслеживайте ход восстановления в журнале сервера. Если это занимает много времени, возможно, идёт воспроизведение большого объёма WAL или есть проблема с восстановлением. Добавьте в приложение логику повтора.","tags":["connection","connection refused","starting up","startup","recovery","crash recovery","57P03"]},{"code":"2BP01","title":"dependent_objects_still_exist","severity":"caution","meaning":"Удаление заблокировано, поскольку от удаляемого объекта зависят другие объекты. DROP завершается ошибкой, пока существуют зависимые объекты.","cause":"Попытка удалить таблицу, от которой зависят представления/внешние ключи/функции; объекты, зависящие от типа или схемы; DROP без CASCADE; столбец/последовательность, на которые ссылаются другие объекты.","fix":"Сначала удалите зависимые объекты или выполните удаление с CASCADE: DROP TABLE my_table CASCADE; (помните, что CASCADE удаляет и зависимые объекты; будьте осторожны). Чтобы увидеть зависимости, прочитайте подробности ошибки (строка DETAIL) или изучите pg_depend/соответствующие каталоги. Правильно спланируйте порядок удаления.","tags":["dependency","dependent objects","cascade","drop","objects","object","2BP01"]},{"code":"55006","title":"object_in_use","severity":"danger","meaning":"Операцию невозможно выполнить над целевым объектом (обычно базой данных), поскольку он используется другими сессиями. Например, базу данных с активными подключениями удалить нельзя.","cause":"Открытые подключения к базе данных во время DROP DATABASE; операция, требующая монопольной блокировки используемого объекта; другая сессия удерживает объект.","fix":"Найдите и завершите открытые подключения к целевой базе данных: SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'db_name' AND pid <> pg_backend_pid(); Заблокируйте новые подключения (при необходимости). В PostgreSQL 13+ удаляйте с принудительным закрытием подключений: 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":"При преобразовании в тип даты/времени (date, time, timestamp) текстовое значение имело недопустимый формат. Значение не соответствует ожидаемому формату даты-времени.","cause":"Текст даты в неожиданном формате (путаница порядка день/месяц/год); недопустимые значения дня/часа; преобразование пустой строки ('') в дату; отсутствующие или лишние компоненты; несоответствие локали/формата; неверная маска формата для to_timestamp/to_date.","fix":"Преобразуйте текст даты с правильным форматом: to_date('2026-07-04', 'YYYY-MM-DD') или to_timestamp(str, 'YYYY-MM-DD HH24:MI:SS'). Предпочитайте формат ISO 8601 (YYYY-MM-DD). Превращайте пустые значения в NULL с помощью NULLIF(value,''). Проверяйте входные данные перед загрузкой; выясните, какая строка некорректна.","tags":["date","date format","datetime","format","timestamp","to_date","22007"]}]},"errors/mysql-more.json":{"engineId":"mysql","engine":"MySQL","order":3,"errors":[{"code":"1052","title":"Столбец 'x' в списке полей неоднозначен","severity":"caution","meaning":"Столбец с одним и тем же именем есть в нескольких таблицах запроса, и поскольку он используется без префикса таблицы/псевдонима, MySQL не может определить, к какой таблице он относится.","cause":"В обеих соединяемых таблицах есть столбец с одинаковым именем (например, id в обеих); столбец записан без квалификации (без префикса) в SELECT, WHERE, ON, GROUP BY или ORDER BY.","fix":"Квалифицируйте каждый неоднозначный столбец именем таблицы или псевдонимом: SELECT a.id, b.id FROM a JOIN b ON a.id = b.a_id; Давайте таблицам короткие псевдонимы и указывайте префикс у всех конфликтующих столбцов. Уменьшайте неоднозначность в соединениях по общим столбцам с помощью USING(my_column).","tags":["ambiguous","ambiguous column","column","column name","join","alias","1052"]},{"code":"1136","title":"Число столбцов не совпадает с числом значений","severity":"caution","meaning":"Число столбцов в операторе INSERT не совпадает с числом значений в списке VALUES. Каждый столбец должен получить ровно одно значение.","cause":"Список столбцов и список значений в INSERT разной длины; в INSERT без списка столбцов число значений не совпадает с числом столбцов таблицы; отсутствующая/лишняя запятая; структура таблицы изменилась, а скрипт не обновлён.","fix":"Уравняйте число столбцов и значений. Всегда используйте явный список столбцов: INSERT INTO my_table (a, b, c) VALUES (1, 2, 3); Проверьте структуру таблицы: DESCRIBE my_table; Сопоставьте значения со столбцами один к одному и исправьте лишние/недостающие запятые.","tags":["column count","column count mismatch","insert","values","mismatch","value count","1136"]},{"code":"1048","title":"Столбец 'x' не может быть null","severity":"caution","meaning":"Предпринята попытка вставить NULL в столбец, определённый как NOT NULL, или присвоить этому столбцу NULL. Обязательное поле не может оставаться пустым.","cause":"Не задано значение для обязательного столбца в INSERT; UPDATE присваивает столбцу NULL; у столбца нет DEFAULT; пустое значение пришло из приложения/подзапроса; строгий режим SQL не заполняет пустое значение молча.","fix":"Передайте допустимое значение для обязательного столбца. Определите для столбца разумное значение по умолчанию: ALTER TABLE my_table MODIFY my_column INT NOT NULL DEFAULT 0; Если правило не нужно, разрешите NULL: ALTER TABLE my_table MODIFY my_column INT NULL; Проверяйте пустые значения в приложении перед отправкой.","tags":["not null","null","empty value","insert","cannot be null","mandatory","1048"]},{"code":"1292","title":"Некорректное значение datetime / усечено некорректное значение","severity":"caution","meaning":"При преобразовании в тип целевого столбца (особенно дату/время или число) значение оказалось недопустимым или некорректным. В строгом режиме MySQL отклоняет его как ошибку.","cause":"Недопустимые даты или значения, такие как '0000-00-00'; неверный формат даты; нечисловой текст в числовом столбце; активен строгий режим SQL (STRICT_TRANS_TABLES); проблемы преобразования из-за другого набора символов/сортировки.","fix":"Приведите значение к допустимому для типа столбца формату ('YYYY-MM-DD' для дат). Используйте NULL вместо пустых/недопустимых дат. При необходимости задайте явный формат через STR_TO_DATE: STR_TO_DATE(str, '%Y-%m-%d'). Если требуется временная терпимость, пересмотрите sql_mode, но предпочтительнее исправить данные. Сужайте поиск, чтобы найти проблемную строку.","tags":["datetime","date","truncated","incorrect value","strict mode","format","1292"]},{"code":"1406","title":"Данные слишком длинные для столбца","severity":"caution","meaning":"Записано значение длиннее заданной максимальной длины столбца. Например, строка из 20 символов не помещается в столбец VARCHAR(10).","cause":"Ширина столбца слишком мала для данных; символы в многобайтовом наборе (utf8mb4) занимают больше места, чем ожидалось; результат конкатенации становится длиннее; активен строгий режим SQL (иначе значение молча усекается).","fix":"Расширьте столбец: ALTER TABLE my_table MODIFY my_column VARCHAR(255); Для очень длинного содержимого рассмотрите тип TEXT. Если значение нужно сократить, обрежьте его в приложении или с помощью SUBSTRING. Найдите переполняющую строку: SELECT * FROM my_table WHERE CHAR_LENGTH(my_column) > 10; Добавьте проверку входных данных.","tags":["data too long","length","varchar","column","column length","truncate","1406"]},{"code":"1071","title":"Указанный ключ слишком длинный; максимальная длина ключа — 767 байт","severity":"caution","meaning":"Создаваемый индекс (или ключ) превысил максимально допустимую длину в байтах. Особенно при многобайтовых наборах символов индексы по VARCHAR быстро достигают этого предела.","cause":"Индекс по широкому столбцу VARCHAR с utf8mb4 (4 байта на символ); старый формат строк InnoDB (REDUNDANT/COMPACT; в 5.7 и ранее также отключённый innodb_large_prefix); суммарная длина составного индекса по нескольким столбцам, превышающая предел; большой первичный ключ.","fix":"Сократите длину индексируемого столбца или используйте префиксный индекс: CREATE INDEX idx ON my_table (my_column(191)); Перейдите к пределу 3072 байта с форматом строк DYNAMIC/COMPRESSED (DYNAMIC используется по умолчанию в MySQL 8.0; innodb_large_prefix удалён в 8.0 и нужен только в 5.7 и ранее). При необходимости пересмотрите набор символов/длину столбца.","tags":["index","index length","key too long","key","prefix","utf8mb4","1071"]},{"code":"1050","title":"Таблица уже существует","severity":"caution","meaning":"Создаваемая таблица уже существует. CREATE TABLE завершился ошибкой, поскольку таблица с таким именем уже есть.","cause":"Повторное создание существующей таблицы; скрипт запущен дважды; миграции применены повторно; то же имя использует другой объект (представление).","fix":"Используйте IF NOT EXISTS, чтобы создавать таблицу, только если она отсутствует: CREATE TABLE IF NOT EXISTS my_table (...); Если действительно нужно пересоздать её, сначала удалите: DROP TABLE IF EXISTS my_table; Выведите список существующих таблиц: SHOW TABLES; Делайте миграции безопасными для повторного запуска (идемпотентными).","tags":["table exists","table already exists","duplicate","duplicate table","create table","1050"]},{"code":"1051","title":"Неизвестная таблица 'x'","severity":"caution","meaning":"Указанная таблица не существует. При удалении, изменении или запросе таблица с таким именем не найдена.","cause":"Опечатка в имени таблицы; таблица никогда не создавалась или уже удалена; выбрана неверная база данных (схема); чувствительность к регистру (в Linux имена таблиц чувствительны к регистру); DROP TABLE для несуществующей таблицы.","fix":"Выведите список существующих таблиц: SHOW TABLES; и выберите правильную базу данных: USE mydb; Исправьте имя таблицы и регистр. Чтобы избежать ошибки при удалении: DROP TABLE IF EXISTS my_table; Если таблица в другой схеме, укажите её как schema.table.","tags":["unknown table","table not found","does not exist","drop","not exists","1051"]},{"code":"1091","title":"Невозможно выполнить DROP 'x'; проверьте, что столбец/ключ существует","severity":"caution","meaning":"Столбец, индекс или внешний ключ, удаляемые через ALTER TABLE, не существуют. Несуществующий объект удалить невозможно.","cause":"Указание несуществующего имени объекта в DROP COLUMN/INDEX/FOREIGN KEY; опечатка в имени; объект уже удалён; путаница имени внешнего ключа с именем индекса; повторный запуск миграции.","fix":"Узнайте настоящее имя объекта: SHOW CREATE TABLE my_table; или SHOW INDEX FROM my_table; Удаляйте его по правильному имени. Для внешнего ключа используйте его символьное имя: ALTER TABLE my_table DROP FOREIGN KEY fk_name; В MySQL нет синтаксиса DROP COLUMN IF EXISTS (он есть только в MariaDB); в идемпотентных миграциях сначала проверяйте существование столбца через 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":"Нельзя указывать целевую таблицу 'x' для обновления в предложении FROM","severity":"caution","meaning":"В операторе UPDATE или DELETE обновляемую/удаляемую таблицу нельзя напрямую использовать как источник в подзапросе (во FROM). MySQL не позволяет одной и той же таблице быть одновременно целью и источником таким образом.","cause":"Подзапрос напрямую читает ту же целевую таблицу, как в UPDATE my_table ... WHERE id IN (SELECT ... FROM my_table ...); подзапрос, ссылающийся на ту же таблицу в DELETE.","fix":"Оберните подзапрос в производную таблицу и дайте ей новый псевдоним, чтобы MySQL рассматривал её как отдельную копию: UPDATE my_table SET c = 1 WHERE id IN (SELECT id FROM (SELECT id FROM my_table WHERE condition) AS t); В качестве альтернативы запишите это как самосоединение: 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 не существует","severity":"caution","meaning":"Вызванная хранимая процедура или функция не найдена в указанной базе данных. Подпрограмма с таким именем не определена.","cause":"Опечатка в имени процедуры; процедура в другой базе данных или никогда не создавалась; через USE выбрана неверная схема; нет привилегии EXECUTE для вызова процедуры; чувствительность к регистру.","fix":"Проверьте, существует ли процедура и в какой схеме: SHOW PROCEDURE STATUS WHERE Db = 'mydb'; Выберите правильную базу данных: USE mydb; или вызывайте с полным именем: CALL mydb.my_procedure(...); Выдайте необходимые привилегии: 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":"Доступ запрещён; требуется (хотя бы одна из) привилегия PRIVILEGE","severity":"danger","meaning":"У пользователя нет привилегии, необходимой для выполняемой операции. Операция требует права, выходящего за рамки разрешений, выданных учётной записи.","cause":"Отсутствует административная привилегия, такая как SUPER, PROCESS, RELOAD или CREATE USER; не выдано разрешение, необходимое для чтения глобального представления/таблицы; конкретная операция (SET GLOBAL, доступ к binlog), требующая повышенных привилегий.","fix":"Просмотрите текущие привилегии: SHOW GRANTS FOR CURRENT_USER(); Выдайте необходимое разрешение от привилегированной учётной записи: GRANT PROCESS ON *.* TO 'app_user'@'host'; (GRANT действует немедленно; FLUSH PRIVILEGES не нужен). По возможности используйте динамические привилегии (например, SYSTEM_VARIABLES_ADMIN) вместо SUPER. Следуйте принципу наименьших привилегий.","tags":["access denied","privilege","missing privilege","permission","grant","access","1227"]},{"code":"3730","title":"Невозможно удалить таблицу, на которую ссылается ограничение внешнего ключа","severity":"danger","meaning":"Таблицу невозможно удалить, поскольку на неё ссылается внешний ключ другой таблицы. Пока ссылка существует, DROP заблокирован.","cause":"Попытка удалить родительскую таблицу, пока существуют связанные с ней через FK дочерние таблицы; несоблюдение порядка зависимостей; внешние ключи не удалены заранее.","fix":"Сначала удалите ссылающийся внешний ключ: ALTER TABLE child_table DROP FOREIGN KEY fk_name; затем удалите таблицу. Найдите зависимые таблицы/ограничения: SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME = 'my_table'; В массовых операциях можно временно использовать SET FOREIGN_KEY_CHECKS = 0;, но будьте осторожны и затем верните значение 1.","tags":["foreign key","fk","drop table","referenced","reference","constraint","3730"]},{"code":"2013","title":"Соединение с сервером MySQL потеряно во время запроса","severity":"danger","meaning":"Соединение между клиентом и сервером MySQL разорвалось во время выполнения запроса. Оно было прервано до того, как сервер успел ответить или вернуть результаты.","cause":"Длительный запрос, превысивший net_read_timeout/net_write_timeout; сервер завершил запрос/соединение из-за нехватки памяти (OOM); обрыв сети; превышение max_allowed_packet; сбой или перезапуск сервера; очень большой набор результатов.","fix":"Проверьте стабильность сети и журнал ошибок сервера; выясните, был ли OOM или сбой. Увеличьте тайм-ауты: SET GLOBAL net_read_timeout = 120; Разбивайте большие запросы на пакеты. Увеличьте max_allowed_packet. Добавьте в приложение логику повторного подключения. Оптимизируйте длительные запросы.","tags":["lost connection","connection dropped","timeout","network","network outage","oom","2013"]},{"code":"1153","title":"Получен пакет больше 'max_allowed_packet' байт","severity":"danger","meaning":"Клиент или сервер попытался отправить пакет данных больше максимально допустимого размера пакета (max_allowed_packet). Обычно это происходит с большими значениями BLOB/TEXT или очень крупными массовыми INSERT.","cause":"Вставка очень больших данных BLOB/TEXT; огромный одиночный INSERT или многострочный массовый INSERT; запрос, возвращающий большой результат; маленькое значение max_allowed_packet; импорт больших строк с помощью mysqldump.","fix":"Увеличьте max_allowed_packet (и на сервере, и на клиенте): SET GLOBAL max_allowed_packet = 67108864; (64MB) и переподключитесь. Чтобы сделать это постоянным, добавьте max_allowed_packet = 64M в раздел [mysqld] файла my.cnf. Разбивайте крупные массовые операции на меньшие пакеты. Оцените, действительно ли нужно хранить очень большие значения.","tags":["max_allowed_packet","packet","packet size","blob","large data","insert","1153"]},{"code":"1969","title":"MariaDB: выполнение запроса прервано (превышен max_statement_time)","severity":"caution","meaning":"Специфично для MariaDB (ER_STATEMENT_TIMEOUT): запрос прерван, поскольку превысил максимальное время выполнения, заданное max_statement_time. В MySQL этого кода нет; эквивалент в MySQL — ошибка 3024 (ER_QUERY_TIMEOUT, max_execution_time).","cause":"В MariaDB — превышение лимита max_statement_time на уровне сессии/глобально или лимита, заданного через SET STATEMENT max_statement_time=N FOR ...; неэффективный план, отсутствующий индекс, сканирование большого объёма данных или ожидание блокировок.","fix":"Проанализируйте запрос с помощью EXPLAIN и ускорьте его, добавив индексы и улучшив план. Если запросу действительно нужно выполняться долго, повысьте лимит в MariaDB: SET SESSION max_statement_time = 60; (секунды) или SET STATEMENT max_statement_time = 60 FOR SELECT ...; Если используете MySQL, см. запись 3024 (max_execution_time, миллисекунды; подсказка MAX_EXECUTION_TIME). Разбивайте крупные операции.","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":"Ошибка деления на ноль","severity":"caution","meaning":"Произошла ошибка деления на ноль, поскольку делитель в арифметическом выражении равен нулю. SQL Server не может выполнить деление и останавливает запрос.","cause":"Столбец или выражение, используемые как делитель, равны 0, нулевой знаменатель при вычислении среднего/отношения, деление без NULLIF или неожиданные нулевые значения в данных.","fix":"Защищайте знаменатель с помощью NULLIF (например, value / NULLIF(denominator, 0)); результат становится NULL, а значение по умолчанию можно задать через ISNULL/COALESCE. Используйте CASE WHEN denominator = 0 THEN NULL ELSE value/denominator END. Отфильтруйте нулевые значения через WHERE перед делением. При необходимости оцените поведение SET ARITHABORT/ANSI_WARNINGS.","tags":["math","arithmetic","divide by zero","division by zero","8134"]},{"code":"245","title":"Ошибка преобразования значения в тип данных","severity":"caution","meaning":"Значение не удалось преобразовать в целевой тип данных. Например, неявное/явное преобразование строкового значения в int или date завершилось ошибкой из-за неподходящего содержимого.","cause":"Преобразование нечислового текста в int/decimal, недопустимый формат даты, неявное преобразование типов при сравнении столбцов, несовместимые типы в условии WHERE или JOIN или некорректные входные данные.","fix":"Используйте безопасные варианты преобразования: TRY_CAST или TRY_CONVERT (возвращают NULL при неудаче). Выявляйте некорректные строки с помощью ISNUMERIC/TRY_CONVERT. Согласуйте типы столбцов и устраните неявные преобразования. Проверяйте входные данные и используйте явный формат для дат (например, стиль CONVERT).","tags":["conversion","type conversion","cast","convert","data type","245"]},{"code":"515","title":"Невозможно вставить значение NULL в столбец","severity":"caution","meaning":"Предпринята попытка вставить или записать значение NULL в столбец, определённый как NOT NULL. Операция отклонена, поскольку столбец не допускает NULL.","cause":"Обязательный столбец пропущен в операторе INSERT, отсутствующие/пустые поля в исходных данных, столбец NOT NULL без значения по умолчанию или NULL, отправленный приложением.","fix":"Передавайте допустимые значения для обязательных столбцов. Добавьте ограничения DEFAULT к подходящим столбцам. Проверяйте исходные данные на отсутствующие поля и назначайте значения по умолчанию через ISNULL/COALESCE. Если столбец на самом деле не обязан быть обязательным, пересмотрите схему, чтобы разрешить NULL.","tags":["null","not null","insert","constraint","constraint violation","515"]},{"code":"8115","title":"Ошибка арифметического переполнения при преобразовании выражения в тип данных","severity":"caution","meaning":"Значение, полученное в результате арифметической операции или преобразования, вышло за диапазон, который может хранить целевой тип данных. Произошло переполнение, поскольку число слишком велико.","cause":"Сумма/произведение (SUM), превышающие диапазон int, большое значение, присвоенное столбцу с маленьким типом, недостаточные точность/масштаб decimal или значение IDENTITY, достигшее предела типа.","fix":"Используйте более широкий тип для промежуточных вычислений (например, SUM с CAST(my_column AS bigint)). Увеличьте точность/масштаб decimal. Расширьте тип столбца под данные. Преобразуйте выражения с риском переполнения до вычисления и ограничивайте диапазоны входных значений.","tags":["overflow","arithmetic overflow","arithmetic","math","int","bigint","8115"]},{"code":"512","title":"Подзапрос вернул более 1 значения","severity":"caution","meaning":"Подзапрос, использованный в контексте, ожидающем одно значение (=, <, >, сравнение или присваивание), вернул больше одной строки. SQL Server ожидал одно скалярное значение, но получил несколько.","cause":"Подзапрос, который предположительно возвращает одно значение для сравнения, возвращает несколько строк, неуникальное условие сопоставления, отсутствующий фильтр или неверно построенный коррелированный запрос.","fix":"Сократите подзапрос до одной строки (например, TOP 1, MAX/MIN или подходящий GROUP BY). Если ожидается несколько значений, используйте IN или EXISTS вместо =. Перепишите с JOIN. Выясните, почему подзапрос возвращает несколько строк, и исправьте условия фильтра/ключа.","tags":["subquery","scalar subquery","more than one","512","in","exists"]},{"code":"207","title":"Недопустимое имя столбца","severity":"caution","meaning":"Имя столбца, на которое ссылается запрос, не найдено в таблице/представлении. Столбца с таким именем нет, или он вне области видимости.","cause":"Ошибка в написании/регистре, неверный псевдоним таблицы, столбец ещё не добавлен или переименован, неверный контекст схемы/таблицы или отсутствие квадратных скобок вместо кавычек.","fix":"Проверьте имя столбца и таблицу (через sys.columns или Object Explorer). Используйте правильные псевдоним и схему. Для недавно добавленных столбцов убедитесь, что объект актуален; при необходимости перекомпилируйте хранимую процедуру. Для имён с пробелами/специальными символами используйте квадратные скобки.","tags":["column","column name","invalid column","invalid column name","alias","207"]},{"code":"156","title":"Неверный синтаксис рядом с ключевым словом","severity":"caution","meaning":"Рядом с ключевым словом есть синтаксическая ошибка. SQL Server не смог разобрать оператор, поскольку зарезервированное слово использовано в неожиданной позиции.","cause":"Использование зарезервированного слова (например, ORDER, USER, TABLE) как имени столбца/объекта без скобок, отсутствующая запятая/скобка, предложения в неверном порядке или неуместно расположенное предложение (WHERE, GROUP BY).","fix":"Изучите область вокруг ключевого слова, на которое указывает ошибка. Если используете зарезервированные слова как имена объектов, заключайте их в квадратные скобки (например, [User]). Исправьте отсутствующие запятые, скобки и порядок предложений. Проверяйте синтаксис, разбивая запрос на части.","tags":["syntax","syntax error","keyword","reserved word","156"]},{"code":"2812","title":"Не удалось найти хранимую процедуру","severity":"caution","meaning":"Хранимая процедура, вызванная через EXEC, не найдена в области текущей базы данных/схемы. Процедуры с таким именем не существует.","cause":"Опечатка в имени процедуры, отсутствующий или неверный префикс схемы (например, пропущен dbo.), процедура находится в другой базе данных, ещё не создана или не видна из-за разрешений.","fix":"Проверьте имя процедуры и схему; используйте в вызове полное имя (например, EXEC dbo.ProcedureName). Убедитесь, что вы в правильной базе данных (USE DatabaseName). Проверьте, существует ли процедура (sys.procedures / OBJECT_ID). Выдайте необходимое разрешение EXECUTE и убедитесь, что скрипт развёртывания был выполнен.","tags":["stored procedure","procedure","sp","not found","procedure not found","2812"]},{"code":"8623","title":"Обработчику запросов не хватило внутренних ресурсов, и он не смог построить план запроса","severity":"danger","meaning":"Обработчик запросов исчерпал внутренние ресурсы на слишком сложном запросе и не смог построить план выполнения. Запрос невозможно оптимизировать, поскольку он слишком большой/сложный.","cause":"Запросы с очень длинными списками IN или множеством предложений OR/UNION, чрезмерное число JOIN, огромные автоматически сгенерированные запросы, запросы со слишком большим числом параметров или чрезмерно глубокие деревья выражений.","fix":"Упростите запрос: перенесите длинные списки IN во временную таблицу/табличный параметр, сократите условия OR с помощью UNION ALL или переписывания и разбейте сложный запрос на части. Сократите число JOIN. Разбивайте автоматически генерируемые запросы на пакеты. При необходимости пересмотрите индексы и дизайн запросов.","tags":["query processor","internal resources","plan","complex query","8623","danger"]},{"code":"4104","title":"Не удалось связать многочастный идентификатор","severity":"caution","meaning":"Многочастный идентификатор, разделённый точками (например, table.column или alias.column), не удалось разрешить. SQL Server не смог связать это имя с таблицей/столбцом в запросе.","cause":"Неверный или неопределённый псевдоним таблицы, ссылка на таблицу, отсутствующую во FROM/JOIN, столбец, отнесённый к неверной таблице, использование настоящего имени таблицы после определения псевдонима или опечатка.","fix":"Убедитесь, что используемые псевдонимы определены в предложении FROM/JOIN. Квалифицируйте столбец правильной таблицей/псевдонимом. Если вы задали таблице псевдоним, используйте его везде (а не настоящее имя). Добавьте недостающую таблицу в JOIN и проверьте имена.","tags":["identifier","multi-part","multi-part identifier","bind","alias","4104"]},{"code":"2714","title":"В базе данных уже есть объект с таким именем","severity":"caution","meaning":"В базе данных уже существует объект (таблица, представление, процедура и т. д.) с тем же именем, что и создаваемый. Оператор CREATE завершился ошибкой из-за конфликта имён.","cause":"Повторный запуск того же скрипта CREATE, существующий объект не удалён заранее, другое предположение о схеме, неидемпотентные скрипты развёртывания/миграции или конфликт имён.","fix":"Сначала проверьте, существует ли объект: создавайте условно с помощью IF OBJECT_ID('dbo.Name') IS NULL или используйте CREATE OR ALTER (для процедур/представлений/функций). При необходимости выполните DROP существующего объекта и создайте его заново. Пишите идемпотентные скрипты миграции. Убедитесь, что работаете в правильной схеме/базе данных.","tags":["already exists","object exists","create","object","duplicate object","2714"]},{"code":"601","title":"Не удалось продолжить сканирование с NOLOCK из-за перемещения данных","severity":"caution","meaning":"Во время сканирования с NOLOCK (READ UNCOMMITTED) сканирование не смогло продолжиться из-за параллельного перемещения данных (разделения/перемещения страниц). Чтение без блокировок наткнулось на несогласованное состояние.","cause":"Сканируемые страницы перемещаются параллельными обновлениями/удалениями/разделениями страниц при чтении с подсказкой NOLOCK или на уровне изоляции READ UNCOMMITTED.","fix":"Повторите запрос (обычно ошибка временная). По возможности избегайте подсказки NOLOCK; для неблокирующего, но согласованного чтения используйте изоляцию READ COMMITTED SNAPSHOT (RCSI). На таблицах с интенсивной записью предпочитайте snapshot isolation грязному чтению.","tags":["nolock","scan","data movement","601","isolation","read uncommitted"]},{"code":"50000","title":"Пользовательская ошибка, вызванная через RAISERROR","severity":"caution","meaning":"Это номер по умолчанию (50000) для пользовательских ошибок, вызванных через RAISERROR без указания ID сообщения. Логика приложения/процедуры намеренно вызвала ошибку.","cause":"Пользовательская ошибка, вызванная через RAISERROR или THROW в хранимой процедуре или скрипте при нарушении бизнес-правила, неудачной проверке, намеренном управлении потоком выполнения или пользовательских сообщениях об исключениях.","fix":"Прочитайте текст сообщения RAISERROR/THROW, чтобы понять фактическое нарушение бизнес-правила. Перехватывайте эту ошибку на стороне приложения с помощью TRY...CATCH и получайте подробности через ERROR_MESSAGE()/ERROR_NUMBER(). Определяйте сообщения через sp_addmessage, чтобы дать пользовательским ошибкам осмысленные номера. Устраните условие, вызывающее ошибку, в бизнес-логике.","tags":["raiserror","custom error","user-defined error","throw","50000","try catch"]},{"code":"3902","title":"Для запроса COMMIT TRANSACTION нет соответствующего BEGIN TRANSACTION","severity":"caution","meaning":"Выполнен COMMIT TRANSACTION, но соответствующего открытого BEGIN TRANSACTION нет. Транзакция для фиксации не найдена, поскольку счётчик транзакций (@@TRANCOUNT) равен нулю.","cause":"Несбалансированное использование BEGIN/COMMIT/ROLLBACK, транзакция уже откатана после ошибки, неверное управление счётчиком вложенных транзакций или повторный вызов COMMIT после ROLLBACK в блоке CATCH.","fix":"Сбалансируйте число операторов BEGIN и COMMIT. Проверяйте @@TRANCOUNT перед фиксацией (IF @@TRANCOUNT > 0 COMMIT). В TRY...CATCH при ошибке выполняйте ROLLBACK на основе XACT_STATE()/@@TRANCOUNT и не допускайте лишнего COMMIT. Пересмотрите логику вложенных транзакций.","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":"Вставляемый или обновляемый документ отклонён, поскольку не удовлетворяет правилам проверки JSON Schema (validator), определённым для коллекции.","cause":"Документ не соответствует схеме: отсутствует обязательное поле, неверный тип BSON, значение вне допустимого диапазона/шаблона, дополнительные поля запрещены или правило нарушено при validationAction 'error'.","fix":"Изучите подробности ошибки (errInfo), чтобы увидеть, какое правило нарушено. Приведите документ в соответствие со схемой: добавьте обязательные поля и исправьте типы. Пересмотрите validator коллекции с помощью collMod. validationLevel/validationAction можно временно ослабить, но для качества данных лучше сохранять правила.","tags":["validation","validation","schema","schema","121","document"]},{"code":"40","title":"ConflictingUpdateOperators","severity":"caution","meaning":"Обновление применило к одному и тому же полю (пути) несколько конфликтующих операторов обновления. MongoDB не может изменить один путь дважды в одной операции.","cause":"Конфликтующие операторы, нацеленные на одно поле в одном документе обновления (например, одновременно $set и $inc или одновременное изменение родительского и дочернего пути), неверно построенное обновление или конфликт вложенных путей.","fix":"Объедините конфликтующие операторы для одного поля в один оператор или разбейте обновление на отдельные операции. Не изменяйте одновременно родительский и дочерний путь (например, a и a.b). Проверьте документ обновления и убедитесь, что каждое поле изменяется только один раз.","tags":["update","update","operator","conflict","conflict","40"]},{"code":"66","title":"ImmutableField (невозможно обновить _id)","severity":"caution","meaning":"Предпринята попытка обновить неизменяемое поле, как правило поле _id документа. _id нельзя изменить после создания документа.","cause":"Обновление, пытающееся изменить поле _id, недопустимое обновление поля ключа шардирования или присвоение нового значения полю, считающемуся неизменяемым.","fix":"Не обновляйте значение _id; если нужен другой _id, вставьте документ с новым _id и удалите старый (insert + delete). Соблюдайте правила MongoDB для обновления ключа шардирования. Удалите неизменяемые поля из документа обновления и задавайте только изменяемые поля.","tags":["immutable","_id","immutable","update","shard key","66"]},{"code":"2","title":"BadValue","severity":"caution","meaning":"В команду или запрос передано недопустимое или неожиданное значение. MongoDB не смог интерпретировать аргумент как допустимое значение.","cause":"Недопустимый аргумент оператора, число вне диапазона, неподдерживаемое значение параметра, некорректная спецификация сортировки, некорректное выражение запроса или неподходящее сочетание параметров.","fix":"Исправьте аргумент по подсказке о поле/значении в сообщении об ошибке. Сверяйте параметры команд и запросов с документацией. Проверьте формат выражений sort/projection/aggregate. Приведите диапазоны и типы значений к допустимым.","tags":["badvalue","invalid value","argument","2","query"]},{"code":"14","title":"TypeMismatch","severity":"caution","meaning":"Тип переданного значения не соответствует ожидаемому типу BSON. Для поля или аргумента ожидался другой тип данных.","cause":"Текст там, где ожидается число, строка вместо даты, одиночное значение там, где ожидается массив, аргумент неверного типа, переданный оператору агрегации/запроса, или несогласованность типов в схеме.","fix":"Передавайте правильный тип BSON для поля/аргумента. Преобразуйте типы на уровне приложения перед сохранением данных (например, записывайте числа как настоящие значения Number). Проверяйте типы, ожидаемые операторами запросов и агрегации. Обеспечьте согласованность типов с помощью валидатора схемы.","tags":["typemismatch","type mismatch","bson","type","14"]},{"code":"10334","title":"BSONObjectTooLarge","severity":"danger","meaning":"Документ BSON превысил максимально допустимый размер (16 MB). MongoDB не может сохранить или вернуть один документ сверх этого предела.","cause":"Превышение предела 16 MB из-за постоянного добавления данных/элементов массива в документ, очень большие вложенные массивы, огромные документы, создаваемые агрегацией, или большие двоичные данные, хранящиеся непосредственно в документе.","fix":"Пересмотрите модель данных: вынесите большие массивы в отдельные коллекции и используйте модель ссылок. Для больших двоичных файлов используйте GridFS. Ограничивайте неограниченно растущие массивы (например, шаблон capped/bucket). Контролируйте размер документов на этапах агрегации.","tags":["bson","16mb","too large","large document","gridfs","10334","danger"]},{"code":"46","title":"LockBusy","severity":"caution","meaning":"Запрошенную блокировку или ресурс не удалось получить немедленно, поскольку их удерживала другая операция, и операция была отклонена вместо ожидания. Ожидания блокировок, завершающиеся тайм-аутом, имеют отдельный код (24 LockTimeout).","cause":"Параллельные административные/DDL-операции над одной коллекцией или базой данных (например, два построения индексов одновременно, collMod, drop или rename), конфликт с выполняющейся операцией обслуживания или внутренняя операция, которая не может даже кратко ждать блокировку, наталкивается на занятый ресурс.","fix":"Найдите конфликтующую операцию: с помощью db.currentOp() изучите DDL-операции или операции обслуживания в том же пространстве имён. Повторите команду после завершения конфликтующей операции. Выполняйте административные/DDL-операции последовательно и не запускайте их параллельно над одной коллекцией. Если ошибка сохраняется, исследуйте в журналах mongod операцию, удерживающую ресурс.","tags":["lock","lock","lockbusy","busy","ddl","46"]},{"code":"96","title":"OperationFailed","severity":"danger","meaning":"Общий сбой операции; при выполнении команды произошла неожиданная ошибка, и операция не смогла завершиться. Это общая ошибка, используемая, когда более конкретного кода нет.","cause":"Внутренняя ошибка на стороне сервера, исчерпание ресурсов, несогласованное состояние, сбой подсистемы (хранение, репликация) или различные условия сбоя, специфичные для конкретной команды.","fix":"Определите первопричину, изучив полный текст сообщения об ошибке и журналы сервера (mongod.log). Повторите операцию; состояние может быть временным. Проверьте ресурсы (диск, память) и состояние replica set. Если проблема повторяется, пересмотрите команду и её входные данные.","tags":["operationfailed","operation failed","generic error","96","danger"]},{"code":"72","title":"InvalidOptions","severity":"caution","meaning":"В команду переданы недопустимые или взаимно несовместимые параметры. Указанное сочетание параметров не поддерживается для этой команды.","cause":"Неподдерживаемое имя параметра, совместное использование взаимоисключающих параметров, параметр, недопустимый для работающей версии, недопустимое сочетание write concern/read concern или конфликт параметров индекса/collation.","fix":"Проверьте по документации параметры, принимаемые командой, и удалите несовместимые. Убедитесь, что ваша версия поддерживает параметр. Пересмотрите настройки write/read concern и collation. Используйте только допустимые сочетания параметров.","tags":["invalidoptions","invalid option","options","72","command"]},{"code":"9","title":"FailedToParse","severity":"caution","meaning":"Сервер не смог разобрать отправленную команду, запрос или выражение. Структура синтаксически недопустима или не соответствует ожидаемому формату.","cause":"Некорректная структура JSON/BSON, отсутствующие или лишние поля, неверное использование операторов, недопустимый синтаксис конвейера агрегации или документ команды, не соответствующий ожидаемому формату.","fix":"Исправьте структуру команды/запроса/конвейера в соответствии с документацией. Проверьте структуру JSON (недостающие скобки/запятые, неверные имена полей). Проверьте синтаксис этапов агрегации. Убедитесь, что операторы используются правильно, а аргументы передаются в ожидаемом формате.","tags":["failedtoparse","parse","parsing","syntax","9","query"]},{"code":"134","title":"ReadConcernMajorityNotAvailableYet","severity":"caution","meaning":"Запрошено чтение с read concern majority, но snapshot, зафиксированный большинством, ещё недоступен. Сервер пока не может обеспечить этот уровень согласованности.","cause":"Replica set только что запущен или нестабилен, записи большинства ещё не зафиксированы, отставание репликации, доступно недостаточно членов или кластер ещё в фазе запуска.","fix":"Немного подождите и повторите; чтение выполнится, как только snapshot большинства станет доступен. Проверьте состояние replica set и отставание репликации (rs.status()). Убедитесь, что работает достаточно членов. Если это не обязательно, рассмотрите более низкий read concern (например, local).","tags":["readconcern","majority","majority","replica","134","consistency"]},{"code":"202","title":"NetworkInterfaceExceededTimeLimit","severity":"danger","meaning":"Сетевой запрос (например, обмен между членами или клиентское подключение) превысил лимит времени. Операция не смогла завершиться из-за сетевого тайм-аута.","cause":"Сетевая задержка/обрыв, медленный или недоступный целевой член, чрезмерная нагрузка, неверно настроенные значения тайм-аутов, проблемы DNS/подключения или географически удалённые члены.","fix":"Проверьте сетевую связность и задержку; убедитесь в доступности членов друг для друга. Проверьте состояние и нагрузку целевого члена. Настройте тайм-ауты (connect/socket timeout) под ваше окружение. Добавьте логику повтора. Если проблема сохраняется, пересмотрите конфигурацию инфраструктуры/сети.","tags":["network","network","timeout","timeout","202","danger"]}]},"errors/redis-more.json":{"engineId":"redis","engine":"Redis","order":6,"errors":[{"code":"NOPERM","title":"NOPERM: у этого пользователя нет прав на выполнение команды или доступ к одному из ключей","severity":"caution","meaning":"Аутентифицированный пользователь ACL не авторизован для команды, которую хочет выполнить, или для ключа, к которому обращается. Redis отклоняет операцию согласно правилам ACL.","cause":"У пользователя ACL нет разрешения на команду/категорию, шаблон ключа отсутствует в его списке разрешённых, действует ограничение на каналы (pub/sub) или пользователю выданы узко ограниченные привилегии.","fix":"Проверьте разрешения пользователя: ACL GETUSER app_user. Добавьте необходимые команды/категории и шаблоны ключей (например, ACL SETUSER app_user +get +set ~app:*). Следуя принципу наименьших привилегий, выдавайте только необходимые разрешения. Убедитесь, что вы подключены под нужным пользователем.","tags":["noperm","acl","privilege","permission denied","permission","user"]},{"code":"ERR value is not an integer or out of range","title":"ERR: значение не является целым числом или вне диапазона","severity":"caution","meaning":"Команда, ожидающая целое число (например, INCR, EXPIRE, count для LPOP), получила нечисловое значение или значение вне диапазона. Redis не смог интерпретировать значение как целое число.","cause":"Ключ, на который нацелен INCR/DECR, содержит текстовое (не целое) значение, аргумент передан как текст вместо числа, число слишком велико/мало для диапазона или отрицательное значение там, где оно недопустимо.","fix":"Проверьте значение ключа: GET mykey и TYPE mykey. Для числовых операций убедитесь, что ключ содержит целое значение. Убедитесь, что аргументы — допустимые целые числа в правильном диапазоне. При необходимости сбросьте значение (SET mykey 0) или создайте ключ заново с правильным типом.","tags":["err","integer","not an integer","range","incr","value"]},{"code":"ERR value is not a valid float","title":"ERR: значение не является допустимым float","severity":"caution","meaning":"Команда, ожидающая десятичное число (float) (например, INCRBYFLOAT, score в ZADD), получила значение, не являющееся допустимым числом с плавающей точкой. Redis не смог разобрать значение как float.","cause":"Ключ, на который нацелен INCRBYFLOAT, не содержит допустимого float, нечисловой аргумент, неверный десятичный разделитель (нужна точка, а не запятая) или некорректно записанное число.","fix":"Проверьте значение ключа через GET и убедитесь, что оно содержит допустимый float. Используйте точку как десятичный разделитель (например, 3.14). Убедитесь, что аргумент числовой и правильно отформатирован. При необходимости сбросьте значение на допустимый float.","tags":["err","float","decimal","incrbyfloat","zadd","value"]},{"code":"ERR increment or decrement would overflow","title":"ERR: инкремент или декремент вызвал бы переполнение","severity":"caution","meaning":"INCR/DECR (или INCRBY/DECRBY) отклонён, поскольку результат вышел бы за пределы диапазона 64-битного знакового целого. Значение превысило бы верхнюю или нижнюю границу.","cause":"Значение ключа очень близко к пределу int64 (около 9,2 квинтиллиона), так что инкремент/декремент вызывает переполнение, очень большой аргумент INCRBY или неконтролируемо растущий счётчик.","fix":"Проектируйте значения счётчиков так, чтобы они оставались в пределах int64. Для больших счётчиков применяйте масштабирование или периодический сброс. Держите приращение (аргумент INCRBY) в разумном диапазоне. Если требуются очень большие значения, используйте другую стратегию подсчёта на уровне приложения.","tags":["err","overflow","integer overflow","incr","decr","int64"]},{"code":"ERR max number of clients reached","title":"ERR: достигнуто максимальное число клиентов","severity":"danger","meaning":"Число подключённых клиентов достигло лимита maxclients, и новые подключения не принимаются. Redis отказывает в дальнейших клиентских подключениях.","cause":"Утечка подключений (подключения никогда не закрываются), недостаточное значение maxclients, неверно настроенный пул подключений, внезапный всплеск числа клиентов или низкий лимит файловых дескрипторов операционной системы.","fix":"Проверьте текущие подключения: CLIENT LIST и INFO clients. Устраните утечки подключений; правильно задайте размер клиентского пула и переиспользуйте подключения. При необходимости повысьте maxclients (CONFIG SET maxclients) и лимит ulimit ОС (файловые дескрипторы). Задайте тайм-аут для простаивающих подключений.","tags":["err","maxclients","connection","client","limit","danger"]},{"code":"BUSYKEY","title":"BUSYKEY: целевое имя ключа уже существует","severity":"caution","meaning":"RESTORE (или похожая команда) завершилась ошибкой, поскольку целевой ключ уже существует. По умолчанию Redis не перезаписывает существующий ключ.","cause":"Имя ключа, восстанавливаемого через RESTORE, уже существует в базе данных, прежние данные не были очищены или несколько попыток восстановления в один и тот же ключ.","fix":"Если нужно перезаписать существующий ключ, добавьте к RESTORE параметр REPLACE. В качестве альтернативы сначала удалите целевой ключ (DEL mykey) или используйте другое имя ключа. Заранее проверяйте существование с помощью EXISTS mykey.","tags":["busykey","restore","already exists","target key","replace","key"]},{"code":"ERR no such key","title":"ERR: такого ключа нет","severity":"caution","meaning":"Ключ, над которым выполняется операция, не найден в базе данных. Команды, требующие существующего ключа, такие как RENAME, DUMP и MOVE, не работают с несуществующим ключом.","cause":"Ключ никогда не создавался, истёк и был удалён по TTL, неверное имя/написание ключа, неверный индекс базы данных (SELECT) или ключ уже удалён через DEL.","fix":"Проверьте, существует ли ключ: EXISTS mykey. Убедитесь, что используете правильное имя ключа и правильную базу данных (SELECT db). RENAME требует существования исходного ключа. Пересмотрите логику для ключей, истекающих по TTL. При необходимости создайте ключ заново.","tags":["err","no such key","key not found","rename","dump","key"]},{"code":"ERR DUMP payload version or checksum are wrong","title":"ERR: неверная версия или контрольная сумма полезной нагрузки DUMP","severity":"caution","meaning":"Сериализованные данные (вывод DUMP), переданные в RESTORE, недействительны; версия несовместима или проверка контрольной суммы не пройдена. Redis не может безопасно восстановить данные.","cause":"Данные DUMP повреждены/изменены, перенос между разными несовместимыми версиями Redis, повреждение при кодировании/декодировании (например, неверная обработка байтов) или отсутствующая/усечённая полезная нагрузка.","fix":"Убедитесь, что данные DUMP передаются без изменений (binary-safe); передавайте их как сырые байты, а не через текстовую кодировку. Убедитесь, что исходная и целевая версии Redis совместимы. Сделайте новый DUMP и передайте его заново. Для миграции рассмотрите команду MIGRATE вместо RESTORE.","tags":["err","dump","restore","checksum","checksum mismatch","version"]},{"code":"NOREPLICAS","title":"NOREPLICAS: недостаточно исправных реплик для записи","severity":"danger","meaning":"Из-за настройки min-replicas-to-write запись отклоняется, поскольку исправных реплик недостаточно. Redis блокирует запись ради сохранности данных.","cause":"Подключено/актуально недостаточно реплик, сбои реплик, отставание репликации, превышающее лимит min-replicas-max-lag, проблемы сети или отсутствующие реплики после failover.","fix":"Проверьте состояние реплик: INFO replication. Верните отказавшие реплики в строй и сократите отставание репликации. Восстановите сетевую связность. Пересмотрите настройки min-replicas-to-write / min-replicas-max-lag под ваше окружение (баланс между надёжностью и доступностью). Обеспечьте достаточное число реплик.","tags":["noreplicas","replica","write","min-replicas","replication","danger"]},{"code":"ERR This instance has cluster support disabled","title":"ERR: в этом экземпляре отключена поддержка кластера","severity":"caution","meaning":"Выполнена команда кластера (CLUSTER ...), но этот экземпляр Redis запущен не в режиме кластера. Команда отклонена, поскольку поддержка кластера отключена.","cause":"Не задан cluster-enabled yes, экземпляр работает в автономном режиме, команды кластера отправляются на автономный сервер или подключение к неверному серверу.","fix":"Если нужны возможности кластера, запустите экземпляр в режиме кластера: задайте cluster-enabled yes в конфигурации, перезапустите и сформируйте кластер из узлов. Если остаётесь в автономном режиме, не используйте команды кластера. Убедитесь, что подключены к правильному серверу (кластерному).","tags":["err","cluster","cluster mode","disabled","standalone","config"]},{"code":"ERR unknown subcommand","title":"ERR: неизвестная подкоманда или неверное число аргументов","severity":"caution","meaning":"Команда-контейнер (например, CONFIG, CLIENT, ACL, XINFO) получила недопустимую подкоманду, или подкоманде передано неверное число аргументов. Redis не распознал подкоманду.","cause":"Опечатка в имени подкоманды, подкоманда отсутствует в используемой версии Redis, неверное/недостаточное число аргументов или неверный синтаксис подкоманды.","fix":"Используйте правильное имя подкоманды и аргументы; обращайтесь к подкоманде HELP соответствующей команды (например, CONFIG HELP, CLIENT HELP). Убедитесь, что ваша версия Redis поддерживает эту подкоманду. Исправьте число аргументов и синтаксис согласно документации.","tags":["err","subcommand","unknown subcommand","argument","config","client"]},{"code":"ERR Client sent AUTH, but no password is set","title":"ERR: AUTH <password> вызван, хотя для пользователя по умолчанию не настроен пароль (Redis 6+) / ERR: клиент отправил AUTH, но пароль не задан (Redis <6)","severity":"caution","meaning":"Клиент попытался пройти аутентификацию через AUTH, но на сервере не задан ни пароль (requirepass), ни пароль ACL. AUTH отклонён, поскольку нет учётных данных для проверки. Redis 6+ в этом случае возвращает \"ERR AUTH <password> called without any password configured for the default user. Are you sure your configuration is correct?\"; \"Client sent AUTH, but no password is set\" — сообщение версий до Redis 6.","cause":"Пустой requirepass на сервере, клиент без необходимости отправляет AUTH, пароль задан в строке подключения, хотя на сервере его нет, или несоответствие конфигурации.","fix":"Если сервер не требует пароля, удалите команду AUTH/настройку пароля из клиента. Если нужна защита паролем, задайте requirepass на сервере (CONFIG SET requirepass password) или определите пароль для пользователя ACL. Согласуйте конфигурацию клиента и сервера.","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":"Запрос не прошёл проверку до выполнения. Обязательное поле отсутствует или поля недопустимы, поэтому Elasticsearch отклоняет операцию.","cause":"Отсутствуют обязательные параметры (например, не указано имя индекса), пустое тело, недопустимое сочетание полей или в запросе bulk/index не передана необходимая информация.","fix":"Изучите список отсутствующих/недопустимых полей в сообщении об ошибке и передайте обязательные поля. Дополните тело запроса и параметры согласно документации API. В bulk-запросах убедитесь, что у каждой строки действия корректные метаданные и тело.","tags":["validation","validation error","request","request error","action"]},{"code":"index_closed_exception","title":"index_closed_exception","severity":"caution","meaning":"Целевой индекс закрыт. Закрытый индекс не обслуживает запросы чтения/записи, поэтому операцию выполнить не удалось.","cause":"Индекс ранее был закрыт через close API, закрыт во время обслуживания/операций ILM или деактивирован для экономии ресурсов.","fix":"Снова откройте индекс: POST /index_name/_open. После открытия дождитесь размещения шардов (green/yellow). Выясните, почему индекс был закрыт (политика ILM, ручное действие). Пересмотрите стратегию хранения редко используемых индексов в закрытом состоянии.","tags":["index closed","closed index","open","index","closed"]},{"code":"unavailable_shards_exception","title":"unavailable_shards_exception","severity":"danger","meaning":"Один или несколько шардов, необходимых для операции, недоступны, поэтому запрос (обычно запись) не смог завершиться. Не найдено достаточно активных копий шардов.","cause":"Неназначенные шарды из-за сбоя/выхода узла, превышение disk watermark, отсутствующие реплики, невыполненное условие wait_for_active_shards или состояние кластера red.","fix":"Проверьте состояние кластера и шардов: GET _cluster/health и GET _cat/shards?v. Выясните, почему шарды не назначены: GET _cluster/allocation/explain. Верните отказавшие узлы, освободите место на диске (watermark) и пересмотрите настройки реплик. При необходимости задайте подходящее значение wait_for_active_shards.","tags":["shard","unavailable","inaccessible","cluster","allocation","danger"]},{"code":"query_shard_exception","title":"query_shard_exception","severity":"caution","meaning":"Запрос завершился ошибкой при выполнении на определённом шарде. Обычно это происходит, когда запрос несовместим с mapping или имеет недопустимую структуру.","cause":"Запрос к несуществующему полю или полю неверного типа, попытка агрегации/сортировки по текстовому полю (fielddata отключена), недопустимый синтаксис запроса, неверный анализ или несовместимый тип поля.","fix":"Изучите поле/причину в подробностях ошибки. Проверьте mapping запрашиваемого поля: GET /index/_mapping. Для сортировки/агрегации используйте подполе keyword (field.keyword) вместо text. Исправьте синтаксис запроса и убедитесь, что типы полей совместимы с запросом.","tags":["query","query error","shard","mapping","fielddata","query shard"]},{"code":"script_exception","title":"script_exception","severity":"caution","meaning":"При компиляции или выполнении скрипта (обычно Painless) произошла ошибка. Скрипт написан неверно или выбросил исключение во время выполнения.","cause":"Синтаксическая ошибка Painless, обращение к значению null, несоответствие типов, использование несуществующего поля/переменной, превышение лимита компиляции скриптов или запрещённый вызов API.","fix":"Изучите подробности компиляции/выполнения в сообщении об ошибке. Исправьте синтаксис скрипта и доступ к полям; добавьте проверки на null (например, doc['field'].size() > 0). Делайте часто используемые скрипты параметризованными (stored/params), чтобы не превышать лимит компиляции. Используйте правильные имена и типы полей.","tags":["script","painless","compilation","runtime","script exception"]},{"code":"number_format_exception","title":"number_format_exception","severity":"caution","meaning":"Значение, которое должно быть числовым, не удалось преобразовать в число. Там, где поле или параметр ожидает число, передана недопустимая/нечисловая строка.","cause":"Нечисловой текст, отправленный в числовое поле (integer/long/double), попытка преобразовать пустую строку в число, неверный десятичный формат или входные данные, несовместимые с mapping.","fix":"Отправляйте в поле допустимые числовые значения. Проверяйте/преобразуйте исходные данные перед индексацией. Для пустых значений используйте null или опускайте поле. Пересмотрите настройку coerce и тип поля в mapping. Используйте точку как десятичный разделитель.","tags":["number format","number","conversion","numeric","format"]},{"code":"connect_transport_exception","title":"connect_transport_exception","severity":"danger","meaning":"Не удалось подключиться к узлу через транспортный уровень (9300). Связь между узлами или между клиентом и кластером не установлена, поэтому операцию выполнить не удалось.","cause":"Целевой узел не работает/недоступен, проблемы сети/межсетевого экрана, неверный транспортный адрес/порт, несоответствие конфигурации TLS, проблема DNS или несоответствие имени/версии кластера.","fix":"Убедитесь, что целевой узел запущен и транспортный порт (по умолчанию 9300) доступен по сети. Проверьте правила межсетевого экрана/сети и конфигурацию discovery. Убедитесь, что настройки TLS/сертификатов согласованы на всех узлах. Изучите журналы узлов и проверьте совместимость cluster.name/версии.","tags":["transport","connection","connect","network","node","danger"]},{"code":"repository_missing_exception","title":"repository_missing_exception","severity":"caution","meaning":"Репозиторий snapshots, указанный в операции snapshot/restore, не найден. Под указанным именем репозиторий не зарегистрирован.","cause":"Репозиторий никогда не регистрировался, опечатка в имени, репозиторий удалён, он определён в другом кластере или не был зарегистрирован повторно после сброса состояния кластера.","fix":"Выведите список зарегистрированных репозиториев: GET _snapshot/_all. Зарегистрируйте репозиторий заново с правильными именем и настройками: PUT _snapshot/repo_name (с type и settings). Убедитесь, что имя репозитория написано правильно. Убедитесь, что доступ к общему репозиторию (fs/s3 и т. д.) работает на всех узлах.","tags":["repository","snapshot repository","snapshot","missing","backup","restore"]},{"code":"concurrent_snapshot_execution_exception","title":"concurrent_snapshot_execution_exception","severity":"caution","meaning":"Новый запрос snapshot/restore отклонён, поскольку одновременно выполняется конфликтующая операция snapshot. В репозитории или индексе ещё идёт другая операция snapshot.","cause":"Предыдущий snapshot ещё выполняется, параллельный конфликт snapshot/restore, затрагивающий тот же индекс, политика SLM (snapshot lifecycle), столкнувшаяся с ручным snapshot, или запуски с короткими интервалами.","fix":"Проверьте выполняющиеся snapshots: GET _snapshot/repo/_current и GET _snapshot/_status. Дождитесь завершения текущей операции или при необходимости отмените её: DELETE _snapshot/repo/snapshot_name. Планируйте snapshots так, чтобы они не перекрывались. Согласовывайте ручные операции с SLM.","tags":["snapshot","concurrent","conflict","simultaneous","slm","backup"]},{"code":"invalid_index_name_exception","title":"invalid_index_name_exception","severity":"caution","meaning":"Указанное имя индекса не соответствует правилам именования Elasticsearch. Имя содержит недопустимые символы или нарушает правила формата.","cause":"Заглавные буквы, запрещённые символы (\\ / * ? \" < > | пробел , #), имя, начинающееся с _, - или +, слишком длинное имя (больше 255 байт) или зарезервированные имена, такие как . / ... (Имена, начинающиеся с точки, не запрещены, но зарезервированы для скрытых/системных индексов и вызывают предупреждение.)","fix":"Приведите имя индекса в соответствие с правилами: используйте только строчные буквы; избегайте запрещённых символов; не начинайте имя с _, - или + и не используйте имена, начинающиеся с точки, кроме системных/скрытых индексов. Держите длину имени меньше 255 байт. Обеспечьте соблюдение этих правил в приложении при генерации имён индексов.","tags":["index name","index naming","invalid","invalid name","naming"]},{"code":"resource_not_found_exception","title":"resource_not_found_exception","severity":"caution","meaning":"Запрошенный ресурс (документ, индекс, задача, pipeline, модель и т. д.) не найден. Ресурса с указанным ID/именем не существует.","cause":"Несуществующий ID документа, индекс/pipeline, который был удалён или никогда не создавался, опечатка в имени, задача ещё не завершена или уже очищена, либо запрос отправлен в неверный кластер.","fix":"Проверьте имя/ID ресурса и его существование (например, GET /index/_doc/id, GET _ingest/pipeline/name). Убедитесь, что отправляете запрос в правильные индекс и кластер. Пересмотрите порядок операций удаления/создания. Для задач проверяйте состояние через API _tasks.","tags":["not found","resource not found","resource","missing resource","404"]},{"code":"process_cluster_event_timeout_exception","title":"process_cluster_event_timeout_exception","severity":"danger","meaning":"Обновление состояния кластера (например, создание индекса, изменение mapping) не завершилось за отведённое время, и истёк тайм-аут. Узел master не смог вовремя обработать обновление.","cause":"Узел master под высокой нагрузкой, очень большое/сложное состояние кластера, частые и интенсивные изменения mapping/шаблонов, медленный master или проблемы с выборами master.","fix":"Проверьте нагрузку и состояние узла master: GET _cluster/health, GET _cat/master и GET _cluster/pending_tasks для ожидающих задач. Уменьшите состояние кластера (сократите лишние индексы/mappings/шаблоны, предотвратите взрывной рост динамического mapping). Выполняйте тяжёлые операции, подобные DDL, пакетами в периоды низкого трафика. При необходимости увеличьте ресурсы master.","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":"Имя уже используется существующим объектом","severity":"caution","meaning":"Имя создаваемого объекта (таблицы, индекса, представления, последовательности, синонима и т. д.) уже используется другим объектом в той же схеме. В Oracle имена объектов уникальны в пределах схемы, и даже объекты разных типов, такие как таблицы, представления и последовательности, разделяют одно пространство имён.","cause":"Имя, выбранное в операторе CREATE, конфликтует с существующим объектом. Частая ловушка: попытка создать последовательность или представление с тем же именем, что у таблицы; или автоматически созданное имя индекса (например, индекса первичного ключа) совпадает с указанным вами именем.","fix":"Выберите другое имя или удалите конфликтующий объект. Чтобы узнать, какой объект использует имя: SELECT object_type, object_name FROM user_objects WHERE object_name = 'YOUR_NAME'; Если действительно нужно его заменить, выполните DROP старого объекта (сначала сделайте бэкап его данных). CREATE OR REPLACE доступен для представлений, синонимов, типов и объектов PL/SQL (procedure, function, package, trigger); для таблиц, индексов и последовательностей его нет.","tags":["ddl","create","name","conflict","duplicate","object"]},{"code":"ORA-02449","title":"На уникальные/первичные ключи таблицы ссылаются внешние ключи","severity":"caution","meaning":"На первичный или уникальный ключ таблицы, которую вы хотите удалить, ссылаются внешние ключи других таблиц. Oracle не позволяет удалить таблицу, не устранив сначала эти зависимости.","cause":"Вы выполнили DROP TABLE без CASCADE CONSTRAINTS, хотя другие таблицы ссылаются на эту таблицу через FK. Обеспечивая ссылочную целостность, Oracle предотвращает появление «осиротевших» внешних ключей.","fix":"Чтобы удалить таблицу вместе с зависимыми внешними ключами: DROP TABLE table_name CASCADE CONSTRAINTS; Это также удаляет связанные ограничения FK в других таблицах (данные в этих таблицах не удаляются). Чтобы увидеть, какие ограничения от неё зависят: 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":"Нет привилегий на табличное пространство 'name'","severity":"danger","meaning":"У пользователя нет квоты на табличное пространство, в которое он пытается писать. В Oracle для выделения места под объект пользователь должен иметь квоту на табличное пространство.","cause":"Пользователю не выдана квота на табличное пространство; обычно новый пользователь без системной привилегии UNLIMITED TABLESPACE пытается создать таблицу/индекс или вставить данные в своё табличное пространство по умолчанию. Возникает при INSERT, CREATE TABLE или росте индекса.","fix":"От имени DBA выдайте пользователю квоту: ALTER USER app_user QUOTA UNLIMITED ON tablespace_name; или конкретный лимит: ALTER USER app_user QUOTA 500M ON users; В качестве альтернативы (используйте осторожно) можно выдать GRANT UNLIMITED TABLESPACE TO app_user;, но это даёт неограниченные права на все табличные пространства и в целом не рекомендуется.","tags":["tablespace","quota","tablespace quota","privilege","privilege","insert"]},{"code":"ORA-01110","title":"data file N: 'path'","severity":"danger","meaning":"Дополнительное (вторичное) сообщение, указывающее на проблему с файлом данных с указанным номером и путём. Оно никогда не появляется само по себе: ему предшествует другая ошибка ORA, сообщающая о фактической проблеме, а эта строка лишь указывает, какой файл данных затронут.","cause":"Обычно сопровождает основную ошибку: файл данных в режиме offline (ORA-00376), требуется восстановление носителя (ORA-01113), повреждение блока (ORA-01578) или файл недоступен для чтения. Причиной может быть отказ диска, случайное удаление, перемещённый файл или незавершённое восстановление из бэкапа.","fix":"Прочитайте фактическую ошибку ORA, предшествующую этой строке, и устраните её. Проверьте затронутый файл: SELECT file#, name, status FROM v$datafile; Если требуется восстановление: RECOVER DATAFILE 'path'; затем ALTER DATABASE DATAFILE 'path' ONLINE; Если файл физически утрачен, восстановите его с помощью 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 не может заархивировать оперативный файл журнала повторного выполнения (online redo log) и поэтому не может повторно использовать эту группу журналов. Когда архивирование застревает в режиме ARCHIVELOG, база данных может перестать принимать новые транзакции и зависнуть.","cause":"Обычно заполнено место назначения архивных журналов или Fast Recovery Area (FRA); закончилось место на диске; место назначения архива недоступно; либо проблема с правами доступа/монтированием. Часто сопровождается ORA-19809 (превышен лимит для файлов восстановления) или ORA-00312.","fix":"Освободите место в месте назначения архива/FRA: сделайте бэкап старых архивных журналов с помощью RMAN в место вне FRA и удалите их (DELETE ARCHIVELOG ALL COMPLETED BEFORE ...; при использовании Data Guard не удаляйте архивные журналы, ещё не переданные/не применённые на standby) или увеличьте db_recovery_file_dest_size. Проверьте состояние мест назначения: ARCHIVE LOG LIST; и SELECT dest_name, status, error FROM v$archive_dest WHERE status != 'INACTIVE'; После устранения проблемы архивирование продолжится с того места, где остановилось.","tags":["archivelog","archive","redo","fra","recovery area","archiver stuck"]},{"code":"ORA-19809","title":"limit exceeded for recovery files","severity":"danger","meaning":"Пространство, выделенное под Fast Recovery Area (FRA) (db_recovery_file_dest_size), заполнено. Без свободного места для архивных журналов, журналов flashback и бэкапов Oracle не может создавать новые файлы восстановления, и база данных может зависнуть.","cause":"Архивные журналы накопились в FRA, потому что их регулярно не копировали в бэкап и не удаляли; выросло использование журналов flashback; либо выделенный размер слишком мал для реальной нагрузки. Встречается вместе с ошибками архивирования, такими как ORA-16038.","fix":"Проверьте использование FRA: SELECT * FROM v$recovery_file_dest; и SELECT * FROM v$flash_recovery_area_usage; Удалите ненужные архивные журналы с помощью RMAN: DELETE ARCHIVELOG ALL BACKED UP 1 TIMES TO DISK; (при использовании Data Guard сначала убедитесь, что они применены на standby; рекомендуется CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY) либо, как постоянное решение, увеличьте размер: ALTER SYSTEM SET db_recovery_file_dest_size = 50G SCOPE=BOTH; Настройте задание RMAN, регулярно копирующее архивные журналы в бэкап.","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":"Файл данных с указанным номером сейчас не может быть прочитан, так как он находится в режиме offline или недоступен. Запросы и операции, обращающиеся к данным в затронутом табличном пространстве, завершаются этой ошибкой.","cause":"Файл данных был переведён в offline автоматически после ошибки или вручную; ожидает восстановления носителя; диск стал недоступен; либо в режиме NOARCHIVELOG в offline остался файл, который невозможно восстановить. Обычно сопровождается строкой ORA-01110, указывающей на файл.","fix":"Посмотрите состояние файлов: SELECT file#, name, status FROM v$datafile WHERE status != 'ONLINE'; Восстановите файл и переведите его в online: RECOVER DATAFILE N; затем ALTER DATABASE DATAFILE N ONLINE; При физическом повреждении/утрате восстановите с помощью RMAN: RESTORE DATAFILE N; RECOVER DATAFILE N; Также устраните исходную проблему с диском/оборудованием.","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":"Listener не смог найти подходящий серверный процесс (handler) для запрошенного типа соединения (dedicated или shared). Клиент может достучаться до listener, но соединение не удаётся передать серверному процессу.","cause":"Экземпляр не зарегистрирован в listener или его регистрация нарушена; достигнут лимит PROCESSES/SESSIONS, и новый выделенный процесс не может быть порождён; в конфигурации shared server нет диспетчеров или их недостаточно; либо экземпляр только что запущен и ещё не зарегистрировался в listener.","fix":"Проверьте службы, которые видит listener: lsnrctl services (число обработчиков и их состояния). Повторно зарегистрируйте экземпляр в listener: ALTER SYSTEM REGISTER; Проверьте лимит PROCESSES: SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); При необходимости увеличьте его с помощью ALTER SYSTEM SET processes=... SCOPE=SPFILE; и перезапустите экземпляр.","tags":["listener","tns","connection","handler","processes","dispatcher"]},{"code":"ORA-28001","title":"the password has expired","severity":"caution","meaning":"Срок действия пароля пользователя базы данных, под которым выполняется подключение, истёк, так как прошёл PASSWORD_LIFE_TIME его профиля. Пока пароль не будет изменён, открыть сеанс невозможно.","cause":"Срок действия пароля, заданный в профиле пользователя, закончился, и льготный период (grace period) тоже истёк. В Oracle 11g и более поздних версиях пароли в профиле DEFAULT истекают каждые 180 дней; это приводит к неожиданным простоям, особенно для долгоживущих сервисных учётных записей приложений.","fix":"Смените пароль: ALTER USER app_user IDENTIFIED BY new_password; Чтобы избежать повторения проблемы с учётными записями приложений, используйте профиль без истечения срока действия пароля: CREATE PROFILE app_profile LIMIT PASSWORD_LIFE_TIME UNLIMITED; затем ALTER USER app_user PROFILE app_profile; Полезен и мониторинг, предупреждающий заранее об истечении срока. Проверьте состояние по ACCOUNT_STATUS в DBA_USERS.","tags":["password","password expired","expired","profile","account","login"]},{"code":"ORA-38029","title":"object statistics are locked","severity":"caution","meaning":"Сбор/удаление статистики отклонены, так как статистика оптимизатора для таблицы (или схемы) заблокирована. Заблокированная статистика защищена от пересчёта с помощью DBMS_STATS.","cause":"Ранее был вызван DBMS_STATS.LOCK_TABLE_STATS или LOCK_SCHEMA_STATS (например, чтобы зафиксировать статистику), либо импорт Data Pump (особенно с CONTENT=METADATA_ONLY) непреднамеренно оставил статистику заблокированной. Запуск GATHER_TABLE_STATS при заблокированной статистике вызывает эту ошибку.","fix":"Разблокируйте статистику: EXEC DBMS_STATS.UNLOCK_TABLE_STATS('APP_SCHEMA','MY_TABLE'); или на уровне схемы DBMS_STATS.UNLOCK_SCHEMA_STATS('APP_SCHEMA'); Если снимать блокировку не нужно, но собрать статистику всё же требуется, используйте FORCE=>TRUE: EXEC DBMS_STATS.GATHER_TABLE_STATS('APP_SCHEMA','MY_TABLE', force=>TRUE); Чтобы посмотреть состояние блокировки: 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":"Оператор сложнее, чем PostgreSQL может обработать; обычно из-за очень глубоко вложенных выражений или множества вложенных операторов дерево выражения превышает лимит стека анализатора/планировщика. Относится к классу 54 (program_limit_exceeded).","cause":"Чрезмерно глубокие скобки/вложенные подзапросы или цепочки OR/AND; огромные автоматически сгенерированные списки WHERE ... IN (...); либо очень большие деревья выражений, возникающие из-за рекурсивных представлений. Достигается лимит max_stack_depth.","fix":"Упростите запрос: перенесите длинные списки IN во временную таблицу или набор VALUES и соединяйте с ним через JOIN; разбейте вложенные выражения; пересмотрите огромные запросы, сгенерированные ORM. Если это действительно необходимо и лимит стека ОС позволяет, осторожно увеличьте max_stack_depth (например, SET max_stack_depth = '4MB';), но оставьте его ниже ulimit ОС.","tags":["program_limit","complex","stack","nested","query","max_stack_depth"]},{"code":"58030","title":"io_error","severity":"danger","meaning":"PostgreSQL столкнулся с ошибкой ввода-вывода (I/O) на уровне операционной системы при чтении или записи файла. Это указывает на физическую проблему или проблему файловой системы при доступе к файлам базы данных. Относится к классу 58 (system_error).","cause":"Отказ диска или сбойные секторы; заполненный диск; ошибки файловой системы; сбой сетевого хранилища (NFS/SAN); проблемы с правами доступа; либо исчерпаны лимиты дескрипторов файлов/inode в ОС.","fix":"Проверьте состояние диска и свободное место (df -h, dmesg, данные SMART). По журналам PostgreSQL определите, какой файл затронут. Исправьте ошибки файловой системы; при необходимости замените оборудование. Если подозревается повреждение данных, запланируйте восстановление из последнего исправного бэкапа и в дальнейшем держите контрольные суммы включёнными (initdb --data-checksums).","tags":["system_error","io","disk","hardware","file","corruption"]},{"code":"22004","title":"null_value_not_allowed","severity":"caution","meaning":"NULL передан туда, где NULL не допускается. Это отличается от ограничения NOT NULL столбца (23502); обычно возникает при передаче NULL в параметр или операцию, не допускающие NULL, в контексте функции или процедуры. Относится к классу 22 (data_exception).","cause":"Присваивание NULL переменной PL/pgSQL, объявленной как NOT NULL; передача NULL в качестве строки запроса в EXECUTE; либо передача NULL в аргументы встроенных функций, которые его не допускают. Примечание: функция с пометкой STRICT не вызывает ошибку при входном NULL — она возвращает NULL, не выполняясь.","fix":"Найдите строку, где происходит сбой (CONTEXT), и защитите поступающее туда значение проверкой на NULL или подставьте значение по умолчанию с помощью COALESCE: COALESCE(value, default_value). В PL/pgSQL проверяйте значения перед присваиванием переменным NOT NULL; в динамическом SQL убедитесь, что строка, передаваемая в EXECUTE, не равна NULL. Не допускайте отправки обязательных полей как NULL на уровне приложения.","tags":["data_exception","null","function","strict","parameter","coalesce"]},{"code":"42P18","title":"indeterminate_datatype","severity":"caution","meaning":"PostgreSQL не смог вывести тип данных выражения или параметра из контекста. Чаще всего встречается, когда нетипизированный заполнитель (например, $1) или голый NULL используется без какой-либо подсказки о типе.","cause":"Нетипизированные параметры в подготовленных операторах; сравнения, где обе стороны неопределённого типа, например \"WHERE $1 = $2\"; либо использование NULL/литералов массивов без контекста. Планировщик не может решить, какой тип использовать.","fix":"Добавьте явное приведение типа к выражению неопределённого типа: $1::text, $1::integer, NULL::date и т. д. Например: SELECT $1::int + 1; Укажите типы для параметров драйвера/ORM. При сравнении столбцов убедитесь, что хотя бы у одной стороны известный тип.","tags":["syntax","type","indeterminate","cast","parameter","prepared"]},{"code":"42P02","title":"undefined_parameter","severity":"caution","meaning":"Запрос ссылается на несуществующий параметр. PostgreSQL сообщает, что указанный позиционный параметр (например, $2) не определён. Относится к классу 42 (syntax_error_or_access_rule_violation).","cause":"Ссылка на большее число параметров, чем передано, в операторе, подготовленном с помощью PREPARE (например, передаются два параметра, а используется $3); пропуски в нумерации параметров; либо неверные индексы $N в динамически склеенном SQL.","fix":"Убедитесь, что номера параметров соответствуют количеству передаваемых значений; проверьте, что они начинаются с $1 и идут без пропусков. Полностью определите типы параметров в операторе PREPARE: PREPARE p(int, text) AS SELECT ... WHERE a=$1 AND b=$2; Если используете ORM/драйвер, сравните количество привязанных параметров с заполнителями в запросе.","tags":["syntax","parameter","prepared","undefined","placeholder","sql"]},{"code":"2F003","title":"prohibited_sql_statement_attempted","severity":"caution","meaning":"Внутри SQL-функции/подпрограммы была предпринята попытка выполнить оператор, запрещённый в этом контексте. Относится к классу 2F (sql_routine_exception). Например, функция, которая должна только читать, пытается изменить данные.","cause":"Ядро PostgreSQL редко выдаёт этот код; обычно он исходит из кода расширения или процедурного языка. Примечание: функция с пометкой IMMUTABLE/STABLE, выполняющая INSERT/UPDATE/DELETE, выдаёт не 2F003, а '... is not allowed in a non-volatile function' с кодом 0A000 (feature_not_supported).","fix":"Прочитайте в сообщении об ошибке, какой оператор был запрещён, и сверьтесь с документацией соответствующей подпрограммы/расширения. Если вы получаете ошибку non-volatile function (0A000), определите изменяющую данные функцию как VOLATILE (это значение по умолчанию): CREATE FUNCTION ... LANGUAGE plpgsql VOLATILE. Уберите изменяющие данные вызовы из мест, которые должны быть только для чтения (индексные выражения, проверки ограничений).","tags":["sql_routine","function","volatile","prohibited","immutable","side effect"]},{"code":"40003","title":"statement_completion_unknown","severity":"danger","meaning":"Результат транзакции неизвестен; соединение с сервером разорвалось до того, как стало ясно, завершилась ли транзакция (особенно во время COMMIT). Клиент не может знать наверняка, зафиксированы ли изменения надёжно. Относится к классу 40 (transaction_rollback).","cause":"После отправки COMMIT, но до получения ответа пропала сеть, сервер аварийно завершился или произошла отработка отказа (failover). Транзакция могла завершиться на сервере, а могла быть откачена.","fix":"Не повторяйте транзакцию вслепую; сначала проверьте, была ли она фактически применена (например, запросом ожидаемой строки). Проектируйте транзакции идемпотентными; используйте уникальные бизнес-ключи/ключи идемпотентности. Реализуйте надёжную логику переподключения и проверки в пуле соединений.","tags":["transaction","commit","unknown","connection","idempotent","failover"]},{"code":"08003","title":"connection_does_not_exist","severity":"danger","meaning":"Предпринята попытка операции над несуществующим (уже закрытым) соединением с базой данных. Приложение попыталось отправить запрос через недействительный дескриптор соединения. Относится к классу 08 (connection_exception).","cause":"Тайм-аут соединения, закрытие сервером, сбой сети или взятое из пула соединение, которое фактически мертво; либо повторное использование соединения, закрытого через close() в коде приложения.","fix":"Включите проверки активности в пуле соединений (validation query / keepalive); настройте параметры, удаляющие мёртвые соединения из пула (например, connectionTestQuery/maxLifetime в HikariCP). В приложении убедитесь, что соединение открыто перед использованием, и автоматически переподключайтесь при его разрыве. Проверьте значения TCP keepalive и idle_in_transaction_session_timeout.","tags":["connection","connection","closed","pool","pool","timeout"]},{"code":"25006","title":"read_only_sql_transaction","severity":"caution","meaning":"Внутри транзакции только для чтения была предпринята попытка выполнить изменяющую данные команду (INSERT/UPDATE/DELETE/DDL). Транзакция настроена только на чтение. Относится к классу 25 (invalid_transaction_state).","cause":"SET TRANSACTION READ ONLY или параметр default_transaction_read_only=on; либо, чаще всего, попытка записи на реплику для чтения (hot standby / потоковая реплика). Серверы standby по определению доступны только для чтения.","fix":"Направляйте запись на основной сервер; проверьте маршрутизацию соединений, разделяющую чтение и запись. Если параметр был установлен по ошибке: SET TRANSACTION READ WRITE; или на уровне сервера default_transaction_read_only=off. Убедитесь, что приложение отправляет трафик чтения-записи на правильный узел.","tags":["transaction","read only","read only","replica","standby","primary"]},{"code":"21000","title":"cardinality_violation","severity":"caution","meaning":"Возвращено больше одной строки там, где ожидалась одна. Скалярный подзапрос вернул несколько строк, либо MERGE/ON CONFLICT DO UPDATE попытался повторно затронуть одну и ту же целевую строку. Относится к классу 21.","cause":"Подзапрос в списке SELECT или в форме WHERE = (subquery), возвращающий несколько строк; один и тот же конфликтный ключ, предложенный более одного раза в одном INSERT ... ON CONFLICT DO UPDATE ('cannot affect row a second time'); либо несколько исходных строк, соответствующих одной целевой строке в MERGE. UPDATE ... FROM не выдаёт ошибку при множественных совпадениях; целевая строка непредсказуемо обновляется только по одной из исходных строк.","fix":"Убедитесь, что подзапрос возвращает одну строку: добавьте LIMIT 1, используйте агрегат (MAX/MIN) или условие, делающее совпадение уникальным. В шаблонах MERGE и ON CONFLICT устраните дубликаты на стороне источника с помощью DISTINCT ON/агрегата; сделайте то же для UPDATE ... FROM, чтобы избежать незаметно неверных результатов. Чтобы найти неожиданные множественные совпадения, выполните подзапрос отдельно и проверьте количество строк.","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: выполняющееся соединение/поток было завершено сервером, выполняемый запрос прерван, а клиентское соединение разорвано. В MySQL этого кода нет; там та же ситуация проявляется как 1317 (ER_QUERY_INTERRUPTED) при прерывании запроса и как 2013 (Lost connection) на стороне клиента при разрыве соединения.","cause":"Администратор или инструмент мониторинга, отправивший команду KILL; начало остановки сервера; лимиты простоя соединений в MariaDB, такие как idle_transaction_timeout/wait_timeout; либо разрыв соединения сервером при нехватке ресурсов. Также встречается во время обслуживания в облачных средах/средах с прокси.","fix":"Выясните, почему запрос был завершён: SHOW PROCESSLIST и журнал медленных запросов для долгих запросов; графики обслуживания/перезапуска сервера; политики завершения соединений. Перехватывайте эту ошибку в приложении и безопасно повторяйте (если операция идемпотентна). Оптимизируйте и разбивайте длинные запросы; используйте автоматическое переподключение в пуле соединений.","tags":["connection","connection killed","kill","thread","shutdown","retry"]},{"code":"1290","title":"ER_OPTION_PREVENTS_STATEMENT: running with the --read-only option","severity":"caution","meaning":"Сервер MySQL отклонил изменяющий данные оператор (INSERT/UPDATE/DELETE/DDL), так как работает с параметром --read-only или --super-read-only. При read_only пользователи с CONNECTION_ADMIN/SUPER всё ещё могут писать; при включённом super_read_only не могут и они, а сообщение звучит как '--super-read-only option'.","cause":"read_only=ON или super_read_only=ON; чаще всего — случайная попытка записи на реплику. Реплики и вторичные узлы Group Replication держатся в режиме только для чтения ради согласованности данных; автоматика отработки отказа переводит бывший primary в super_read_only. Запись на неправильный узел во время failover/switchover также вызывает эту ошибку.","fix":"Направляйте запись на основной (source) сервер. Если этот сервер действительно должен стать новым primary: SET GLOBAL read_only = OFF; (отключение read_only автоматически отключает и super_read_only; включение super_read_only включает и read_only). Не меняйте это вручную на узлах, управляемых вашим HA-инструментом (Orchestrator/Group Replication/InnoDB Cluster). Чтобы проверить состояние: 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 работает в режиме только для чтения (innodb_read_only=ON), либо каталог данных/носитель доступен только для чтения; запись и DDL для таблиц InnoDB отклоняются. Примечание: попытка записи при включённом read_only/super_read_only возвращает 1290 (ER_OPTION_PREVENTS_STATEMENT), а не этот код.","cause":"Сервер был запущен с innodb_read_only=ON (например, носитель только для чтения, общий каталог данных, копия для анализа); в этом режиме изменения, такие как INSERT/UPDATE/DELETE и CREATE/DROP/ALTER TABLE, для таблиц InnoDB невозможны.","fix":"Если запись нужна, запустите сервер без innodb_read_only: удалите строку innodb_read_only=ON из my.cnf и перезапустите (эта переменная не динамическая и требует перезапуска). Чтобы проверить состояние: SELECT @@innodb_read_only; Убедитесь, что каталог данных и файловая система доступны для записи. Если вы пишете на реплику/вторичный HA-узел (1290), направьте запись на 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":"Вставляемая или обновляемая строка нарушает ограничение CHECK, определённое для таблицы. В MySQL 8.0.16 и более поздних версиях ограничения CHECK действительно проверяются (более ранние версии их игнорировали).","cause":"Значение столбца не удовлетворяет условию в выражении CHECK (например, ввод отрицательной цены при наличии CHECK (price >= 0)). После обновления со старых версий MySQL до 8.0.16+ ошибка может появиться неожиданно, так как ранее игнорировавшиеся ограничения теперь проверяются.","fix":"Убедитесь, что значение удовлетворяет условию ограничения; добавьте проверку на стороне приложения. Чтобы увидеть определение ограничения: SHOW CREATE TABLE my_table; Если ограничение действительно не нужно: ALTER TABLE my_table DROP CHECK constraint_name; или временно ALTER TABLE my_table ALTER CHECK constraint_name NOT ENFORCED; Также устраните источник некорректных данных.","tags":["check","constraint","check constraint","validation","8.0","violation"]},{"code":"1118","title":"ER_TOO_BIG_ROWSIZE: Row size too large","severity":"caution","meaning":"Размер строки определяемой таблицы превышает максимальный размер строки, допустимый подсистемой хранения. Максимальный размер строки для столбцов, не являющихся BLOB/TEXT, — 65535 байт; у InnoDB есть дополнительные лимиты, зависящие от размера страницы.","cause":"Определение множества широких столбцов (особенно длинных VARCHAR/CHAR); каждый символ занимает до 4 байт в многобайтовых кодировках, таких как utf8mb4; либо большие столбцы, хранящиеся внутри строки в форматах строк COMPACT/REDUNDANT InnoDB.","fix":"Преобразуйте часть больших столбцов в TEXT/BLOB (их можно хранить вне страницы); уменьшите ненужные или очень широкие столбцы либо разделите таблицу на две. В InnoDB помогает ROW_FORMAT=DYNAMIC или COMPRESSED, вынося большие столбцы за пределы страницы: ALTER TABLE my_table ROW_FORMAT=DYNAMIC; Также учитывайте innodb_page_size.","tags":["row size","row size too large","innodb","varchar","row_format","limit"]},{"code":"1170","title":"ER_BLOB_KEY_WITHOUT_LENGTH: BLOB/TEXT column used in key specification without a key length","severity":"caution","meaning":"Вы использовали столбец BLOB или TEXT в определении индекса/ключа без указания длины префикса. Эти типы нельзя проиндексировать целиком; индексировать можно только первые N символов/байт.","cause":"Определение INDEX/UNIQUE/PRIMARY KEY на столбце TEXT/BLOB без длины в CREATE TABLE или CREATE INDEX. MySQL требует длину префикса, так как не может индексировать целиком эти потенциально очень большие значения.","fix":"Укажите длину префикса в индексе, например INDEX (description(255)). Пример: ALTER TABLE my_table ADD INDEX idx_description (description(191)); (191 часто используется с utf8mb4 из-за лимита префикса ключа InnoDB). Если нужен полнотекстовый поиск, рассмотрите индекс FULLTEXT. Для полей, которые должны быть короткими и уникальными, может быть уместнее преобразовать столбец в VARCHAR.","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":"При использовании формата binlog на основе операторов (STATEMENT) был выполнен оператор с недетерминированным результатом (способный дать другой результат на реплике). MySQL выдаёт предупреждение при записи его в binlog, так как есть риск расхождения данных между source и репликой.","cause":"При binlog_format=STATEMENT: недетерминированные функции, такие как UUID(), SYSDATE() или USER() (NOW() реплицируется безопасно при STATEMENT), UPDATE/DELETE с LIMIT, но без ORDER BY, INSERT в сочетании с AUTO_INCREMENT или некоторые пользовательские/системные функции. Эти операторы могут давать разные результаты на разных серверах.","fix":"Переключите формат binlog на построчный (рекомендуется; ROW используется по умолчанию начиная с MySQL 5.7.7): binlog_format=ROW в my.cnf или SET PERSIST binlog_format = ROW; (переменная binlog_format устарела начиная с 8.0.34; изменение вступает в силу для новых сеансов). Формат ROW безопасно реплицирует недетерминированные операторы. Если необходимо остаться на STATEMENT, замените недетерминированные функции/выражения константами и добавьте детерминированный ORDER BY в DML с LIMIT.","tags":["binlog","replication","unsafe","statement","row","nondeterministic"]},{"code":"3140","title":"ER_INVALID_JSON_TEXT: Invalid JSON text","severity":"caution","meaning":"В столбец JSON или функцию JSON передан недопустимый (синтаксически некорректный) текст JSON. MySQL отклоняет операцию, так как не может разобрать значение как JSON.","cause":"Незакрытая кавычка/скобка, завершающая запятая, одинарные кавычки, ошибки экранирования или обычный текст, отправленный в столбец JSON. Появляется, когда некорректный текст передаётся в функции, такие как JSON_SET, JSON_INSERT, или в столбец типа JSON.","fix":"Исправьте текст JSON до корректного синтаксиса: используйте двойные кавычки, уберите завершающие запятые, проверьте экранирование. Проверьте, корректен ли текст: SELECT JSON_VALID('{...}'); (возвращает 1, если корректен). Используйте надёжную библиотеку для сериализации JSON на уровне приложения и проверяйте его перед отправкой на сервер.","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":"Значение ключа секционирования вставляемой строки не попадает ни в одну определённую секцию. При секционировании RANGE или LIST вставка завершается ошибкой, так как ни одна секция не охватывает поступившее значение.","cause":"При секционировании RANGE — вставка значения выше наибольшей границы без определённой секции MAXVALUE (например, будущей даты); при секционировании LIST — поступление значения, отсутствующего в списках. Часто встречается в таблицах, секционированных по времени, когда секция для нового периода не добавлена вовремя.","fix":"Добавьте секцию, охватывающую это значение. Новая верхняя секция для RANGE: ALTER TABLE my_table ADD PARTITION (PARTITION p2026 VALUES LESS THAN (2027)); Для секции, принимающей все остальные значения, определите MAXVALUE для RANGE и соответствующие значения для LIST. Для таблиц, секционированных по времени, настройте задание обслуживания (event/scheduler), автоматически добавляющее секции.","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 попытался выделить новую страницу для объекта, но в файловой группе недостаточно места на диске. Файлы данных не могут ни вырасти, ни найти свободную страницу, поэтому запись завершается ошибкой.","cause":"Диск с файлом данных заполнен; для файла отключено автоувеличение (autogrow) или достигнут MAXSIZE; либо все файлы в файловой группе достигли предельного размера. Часто встречается при больших INSERT/построении индексов.","fix":"Освободите место на диске или добавьте место в файловую группу. Увеличьте файл или включите autogrow: ALTER DATABASE db MODIFY FILE (NAME = logical_name, SIZE = 20GB, MAXSIZE = UNLIMITED, FILEGROWTH = 512MB); В качестве альтернативы добавьте в файловую группу новый файл (на другом диске). Чтобы увидеть использование файлов, проверьте sys.database_files, а свободное место — с помощью 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":"На удаляемый объект (таблицу, тип, функцию и т. д.) ссылается другой объект, и эта зависимость препятствует удалению. Так защищены, в частности, объекты, привязанные к схеме.","cause":"Попытка удалить таблицу/объект, от которого зависит представление или функция, созданные WITH SCHEMABINDING; удаление таблицы, на которую ссылается внешний ключ; либо удаление пользовательского типа, пока его ещё использует таблица/параметр.","fix":"Сначала удалите ссылающиеся объекты или устраните зависимость. Чтобы найти зависимости: SELECT referencing_schema_name, referencing_entity_name FROM sys.dm_sql_referencing_entities('app_schema.object', 'OBJECT'); Для представлений, привязанных к схеме, сначала уберите SCHEMABINDING с помощью ALTER VIEW или удалите представление, затем удалите таблицу.","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":"Транзакция, работающая на уровне изоляции snapshot, завершилась ошибкой, так как объект, к которому она обращалась, был изменён оператором DDL (изменение схемы) в другой параллельной транзакции. Метаданные не версионируются, поэтому транзакция snapshot не может продолжаться.","cause":"Пока выполняется транзакция, начатая на уровне изоляции SNAPSHOT (SET TRANSACTION ISOLATION LEVEL SNAPSHOT), другой сеанс выполняет DDL, такой как ALTER TABLE/CREATE INDEX, над таблицей, к которой она обращается. Транзакции snapshot видят версии строк, но не версии схемы. Эта ошибка не возникает при READ COMMITTED SNAPSHOT (RCSI); RCSI создаёт новый снимок для каждого оператора.","fix":"Повторите транзакцию; обычно она успешна после завершения DDL. В качестве постоянного решения выполняйте изменения схемы (DDL) в окна обслуживания без интенсивной нагрузки snapshot. Перехватывайте эту ошибку в приложении и реализуйте логику автоматического повтора. Сокращайте длительные транзакции snapshot.","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":"Добавляемый внешний ключ может создать цикл или несколько путей каскадного удаления/обновления к одной и той же таблице при каскадных операциях (ON DELETE/UPDATE CASCADE). SQL Server отклоняет его, чтобы избежать неоднозначного каскадного поведения.","cause":"FK с каскадным действием, создающий циклическую ссылку между таблицами (A->B->A) или несколько каскадных путей к одной таблице. SQL Server запрещает это, чтобы предотвратить неоднозначный порядок каскада и бесконечные циклы.","fix":"Определите внешний ключ как ON DELETE NO ACTION / ON UPDATE NO ACTION (уберите каскад): ALTER TABLE child ADD CONSTRAINT fk ... FOREIGN KEY (...) REFERENCES parent(...) ON DELETE NO ACTION; Реализуйте необходимое каскадное удаление с помощью триггера INSTEAD OF DELETE: триггер сначала удаляет дочерние строки, затем родительскую строку (например, 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;). Триггер AFTER DELETE не подойдёт: проверка FK выполняется до триггера, поэтому удаление отклоняется с ошибкой 547. Пересмотрите циклические зависимости в модели данных.","tags":["foreign key","fk","cascade","circular reference","cycle","constraint"]},{"code":"9421","title":"XML parsing: illegal xml character","severity":"caution","meaning":"SQL Server обнаружил недопустимый (некорректный) символ при разборе текста как XML. Преобразование в тип XML или присваивание столбцу/переменной XML завершилось ошибкой из-за этого символа.","cause":"Управляющие символы, запрещённые в XML 1.0 (например, некоторые символы в диапазоне 0x00-0x1F), неверная кодировка (несоответствие encoding) или повреждённые/неочищенные данные из другого источника. Обычно появляется при CAST(... AS XML) или INSERT в столбец XML.","fix":"Удалите недопустимые символы из текста перед преобразованием в XML. Согласуйте исходную кодировку (UTF-8/UTF-16) с целевой; укажите правильную кодировку в XML-объявлении. Удалите управляющие символы с помощью REPLACE/NCHAR либо формируйте и проверяйте XML стандартной библиотекой на уровне приложения. Если необходимо хранить исходный текст, рассмотрите 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":"Указанный объект или участник безопасности (пользователь, роль, имя входа) либо не существует, либо у вас нет разрешения на доступ к нему или действия с ним. По соображениям безопасности SQL Server объединяет оба случая в одно сообщение.","cause":"Использование несуществующего имени пользователя, роли, схемы или объекта в команде GRANT/ALTER/DROP и т. п.; работа в неверном контексте базы данных (объект находится в другой БД); либо отсутствие привилегии для операции. Частые причины — опечатки, регистр и префиксы схемы.","fix":"Убедитесь, что имя объекта/участника написано правильно и вы находитесь в нужной базе данных. Для пользователей: SELECT name, type_desc FROM sys.database_principals; для объектов выполните запрос к sys.objects. Убедитесь, что у выполняющей учётной записи есть необходимые разрешения (например, ALTER ANY USER, CONTROL). Используйте правильный префикс схемы (например, 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":"У вас нет разрешения, необходимого для массовой загрузки с помощью BULK INSERT (или OPENROWSET BULK). Массовая загрузка напрямую обращается к файловой системе и ресурсам сервера, поэтому требует отдельной привилегии.","cause":"У имени входа нет разрешения уровня сервера ADMINISTER BULK OPERATIONS, или оно не входит в серверную роль bulkadmin (в Azure SQL Database вместо этого требуется разрешение уровня базы данных ADMINISTER DATABASE BULK OPERATIONS). Также требуются разрешение INSERT на целевую таблицу и доступ на чтение исходного файла для учётной записи службы SQL Server.","fix":"Выдайте необходимое разрешение: GRANT ADMINISTER BULK OPERATIONS TO login_name; (на уровне сервера) или добавьте пользователя в соответствующую роль. В Azure SQL DB используется ADMINISTER DATABASE BULK OPERATIONS. Также убедитесь, что у учётной записи службы SQL Server есть разрешение на чтение целевого файла/общего ресурса. Следуя принципу наименьших привилегий, выдавайте его только тем учётным записям, которым оно нужно.","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":"В Azure SQL Database база данных достигла лимита ресурсов (например, параллельных запросов/рабочих потоков или сеансов), и новый запрос был отклонён. Это означает, что достигнут потолок выбранного уровня обслуживания (DTU или vCore).","cause":"Превышение максимального числа параллельных запросов (рабочих потоков) или сеансов, допустимого выбранным уровнем; внезапный всплеск нагрузки; неэффективные/долгие запросы, занимающие рабочие потоки; либо слишком большой пул соединений.","fix":"Снизьте параллелизм: оптимизируйте запросы, чтобы сократить их длительность, закройте ненужные открытые транзакции, держите размер пула соединений разумным. При постоянной нагрузке повысьте уровень обслуживания (больше DTU/vCore). Отслеживайте использование ресурсов с помощью sys.dm_db_resource_stats. Для ошибок, допускающих повтор, реализуйте в приложении повтор с экспоненциальной задержкой.","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 достигла максимальной квоты размера данных, установленной для уровня обслуживания. База данных больше не может расти, поэтому новые операции записи завершаются ошибкой.","cause":"Рост данных до лимита уровня; большие индексы/журнал, занимающие место; либо небольшой MAXSIZE, допустимый выбранным уровнем (например, Basic/Standard серии S). Неархивированные исторические данные также быстро заполняют квоту.","fix":"Освободите место: удалите/заархивируйте ненужные данные, удалите неиспользуемые индексы, сожмите таблицы (сжатие данных). В качестве постоянного решения увеличьте MAXSIZE или перейдите на более высокий уровень обслуживания: ALTER DATABASE db MODIFY (MAXSIZE = 250 GB); Отслеживайте использование размера с помощью sys.database_files и связанных DMV. Планируйте масштабирование заранее, исходя из тенденции роста.","tags":["azure","size","quota","maxsize","size quota","scaling"]},{"code":"40613","title":"Database is not currently available (Azure SQL)","severity":"danger","meaning":"В Azure SQL целевая база данных в данный момент недоступна. Обычно это временно: ошибка появляется ненадолго, пока платформа перемещает, перенастраивает или восстанавливает базу данных.","cause":"Автоматическое масштабирование/отработка отказа, установка исправлений, балансировка нагрузки или перемещение ресурсов на стороне Azure; изредка — более длительное событие платформы. Соединения отклоняются, пока база данных снова не станет доступна.","fix":"Немного подождите и повторите попытку; это временная ошибка. Реализуйте в приложении автоматический повтор с экспоненциальной задержкой; Azure SDK и библиотеки повторов распознают этот код ошибки как временный. Если проблема сохраняется, проверьте Azure Service Health и региональные инциденты. Для высокой доступности рассмотрите георепликацию/группу отработки отказа.","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":"Указанная роль не определена в MongoDB. При назначении роли пользователю, обновлении роли или запросе привилегий роли использовано несуществующее имя роли.","cause":"Опечатка или так и не созданное имя роли в grantRolesToUser/createUser; поиск роли в неверной базе данных (роли привязаны к конкретной БД); либо пользовательская (не встроенная) роль, ещё не определённая с помощью createRole.","fix":"Проверьте имя роли и базу данных, к которой она относится. Выведите существующие роли: db.getRoles({showBuiltinRoles: false}) или соответствующий административный запрос по всем БД. Если пользовательской роли нет, сначала создайте её: db.createRole({role:'reader', privileges:[...], roles:[]}). При назначении укажите правильную db: 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":"Внутри многодокументной транзакции предпринята попытка операции, не поддерживаемой в этом контексте. Некоторые команды (в частности, определённые операции DDL и с метаданными) не могут выполняться внутри транзакции.","cause":"Неподдерживаемые операции внутри транзакции, например создание коллекций/индексов (в зависимости от версии); некоторые команды типа count/list; либо административные команды, недопустимые в сеансе транзакции. Старые версии MongoDB не поддерживают DDL внутри транзакций (частично поддержка появилась в 4.4+).","fix":"Вынесите неподдерживаемую операцию за пределы транзакции (например, создайте нужные коллекции/индексы до начала транзакции). Проверьте версию MongoDB; 4.4+ поддерживает часть DDL внутри транзакций. Выполните операцию в отдельном сеансе драйвера. Ограничьте транзакцию поддерживаемыми операциями CRUD.","tags":["transaction","multi-document transaction","ddl","unsupported","session","crud"]},{"code":"20 IllegalOperation","title":"Illegal operation","severity":"caution","meaning":"Запрошенная операция недопустима/запрещена для текущего состояния сервера или типа целевого объекта. MongoDB возвращает эту общую ошибку, когда отклоняет логически недопустимое действие.","cause":"Попытка выполнить операцию, предназначенную только для коллекций, над представлением; недопустимое переименование между двумя базами данных; запрещённое изменение системных коллекций; либо административное действие, недопустимое в конфигурации набора реплик/шардинга. Зависит от контекста.","fix":"Прочитайте подробности сообщения об ошибке; обычно в нём прямо указано, какая операция запрещена и почему. Адаптируйте операцию к типу целевого объекта (например, работайте с базовой коллекцией вместо представления). Для операций переименования/перемещения изучите ограничения MongoDB (например, renameCollection в пределах одной БД). Проверьте состояние кластера (набор реплик/шардинг).","tags":["illegal","invalid","operation","view","rename","system"]},{"code":"48 NamespaceExists","title":"Namespace (collection) already exists","severity":"safe","meaning":"Коллекция (пространство имён), которую вы пытаетесь создать, уже существует. MongoDB сообщает, что она есть, поэтому создавать новую коллекцию с тем же именем и параметрами не нужно.","cause":"Попытка повторно создать существующую коллекцию с помощью createCollection (возможно, с другими параметрами); многократный запуск скриптов схемы/настройки; либо попытка переопределить существующее имя как capped-коллекцию или с особыми параметрами.","fix":"Обычно эту ошибку можно безопасно игнорировать: она означает, что коллекция уже существует. Сделайте скрипты настройки идемпотентными: проверяйте наличие перед созданием (db.getCollectionNames().includes('name')). Чтобы изменить параметры, нужно удалить и заново создать коллекцию (предварительно сделайте бэкап данных): db.my_collection.drop(), затем createCollection.","tags":["namespace","collection","collection exists","createcollection","already exists","idempotent"]},{"code":"11601 Interrupted","title":"Operation was interrupted","severity":"caution","meaning":"Выполнявшаяся операция была прервана. MongoDB завершила её до окончания из-за внешнего вмешательства или изменения состояния сервера.","cause":"Администратор/инструмент завершил операцию с помощью db.killOp(); начало остановки сервера; смена primary в наборе реплик (stepdown/failover). (Превышение maxTimeMS возвращает 50 MaxTimeMSExpired, а не эту ошибку.) Так можно остановить долгие запросы.","fix":"Выясните, почему операция была прервана: история currentOp/killOp, события отработки отказа, обслуживание сервера. Если операция идемпотентна, безопасно повторите её. Оптимизируйте и сокращайте длинные запросы; добавьте подходящие индексы. Используйте в драйвере логику повтора при временных ошибках; обеспечьте автоматическое переподключение при отработке отказа набора реплик.","tags":["interrupted","interruption","killop","failover","stepdown","retry"]},{"code":"85 IndexOptionsConflict","title":"Index already exists with different options / different name","severity":"caution","meaning":"Создаваемый индекс конфликтует с существующим: у индекса с тем же именем другие параметры, либо эквивалентный индекс с тем же ключом и параметрами уже существует под другим именем. MongoDB не позволяет повторно создать тот же индекс с конфликтующим определением.","cause":"Создание индекса с тем же именем и ключом, но другими параметрами (например, expireAfterSeconds, partialFilterExpression, collation); либо переопределение эквивалента существующего индекса под другим именем ('Index already exists with a different name'). Типичная причина — расхождение определений индексов со временем в скриптах приложения/миграций. То же имя с другим ключом даёт вместо этого 86 IndexKeySpecsConflict.","fix":"Посмотрите существующий индекс: db.my_collection.getIndexes(). Если вы действительно хотите изменить параметры, удалите старый индекс и создайте новый: db.my_collection.dropIndex('index_name'), затем createIndex(...). В противном случае сделайте определение в точности совпадающим с существующим индексом или дайте новому индексу другое имя. Сделайте скрипты миграций идемпотентными.","tags":["index","indexes","options","conflict","unique","createindex"]},{"code":"67 CannotCreateIndex","title":"Cannot create index","severity":"caution","meaning":"Построение индекса завершилось ошибкой из-за ограничения или лимита. MongoDB не смогла построить запрошенный индекс из-за существующих данных или конфигурации.","cause":"Превышение максимального числа индексов на коллекцию (64); слишком большой ключ; либо несовместимые сочетания параметров (например, неподходящий тип поля для TTL-индекса). Примечание: построение уникального индекса при наличии дубликатов в существующих данных вызывает E11000 DuplicateKey, а не этот код.","fix":"Если достигнут лимит числа индексов, удалите неиспользуемые индексы (проанализируйте использование с помощью $indexStats). Проверьте совместимость размера/типа ключа и сочетание параметров. В больших коллекциях стройте индекс при низкой нагрузке (начиная с 4.2 построение индекса в любом случае не блокирует запись надолго; параметр background игнорируется).","tags":["index","indexes","unique","duplicate","limit","ttl"]},{"code":"27 IndexNotFound","title":"Index not found","severity":"safe","meaning":"Указанный индекс не найден в коллекции. Обычно появляется при попытке удалить или переименовать несуществующий индекс.","cause":"Неверное имя индекса или неверная спецификация ключа в команде dropIndex/hideIndex и т. п.; индекс был удалён ранее; либо автоматически сгенерированное имя индекса отличается от ожидаемого (MongoDB генерирует имена вида field_1_field2_-1).","fix":"Выведите существующие индексы и их точные имена: db.my_collection.getIndexes(). Повторите операцию с правильным именем или документом ключа: db.my_collection.dropIndex({field: 1}) или db.my_collection.dropIndex('field_1'). Пишите скрипты удаления так, чтобы они не падали при отсутствии индекса (с проверкой существования).","tags":["index","indexes","dropindex","not found","getindexes","name"]},{"code":"79 UnknownReplWriteConcern","title":"Unknown replica set write concern","severity":"caution","meaning":"Указанный write concern не распознан. Значение w, использованное в операции (имя пользовательского/тегированного write concern или числовое значение), не определено в конфигурации набора реплик.","cause":"Использование имени пользовательского write concern, не определённого в getLastErrorModes (например, опечатка вместо 'majority' или имя пользовательского тега). Конфигурация и запрос не совпадают. Примечание: запрос числового w больше числа голосующих членов — это другая ошибка (100 UnsatisfiableWriteConcern).","fix":"Убедитесь, что используемое имя write concern определено в настройках набора реплик. Посмотрите текущую конфигурацию: rs.conf() (settings.getLastErrorModes). Если используете пользовательский режим, сначала определите его либо перейдите на стандартные значения ('majority' или допустимое числовое w). Проверьте параметр writeConcern в драйвере/строке подключения приложения.","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":"Вы попытались выполнить команду DEBUG, но сервер её не разрешает. DEBUG — низкоуровневая, опасная диагностическая команда; по соображениям безопасности в новых версиях Redis она по умолчанию отключена или ограничена.","cause":"В Redis 7+ подкоманды DEBUG не выполняются без параметра enable-debug-command; управляемые облачные сервисы Redis обычно полностью отключают команду; либо команда запрещена через ACL. Тестовые/диагностические скрипты (например, DEBUG SLEEP, DEBUG OBJECT) часто сталкиваются с этой ошибкой.","fix":"Избегайте DEBUG в продуктиве. Если она действительно нужна в локальной/тестовой среде, включите её в конфигурации: enable-debug-command yes (или 'local') в redis.conf. В облачных сервисах команду обычно вообще нельзя включить; для получения аналогичной информации используйте безопасные команды, такие как MEMORY USAGE, OBJECT ENCODING и INFO. Если действует ограничение ACL, проверьте разрешения.","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 не удалось создать дочерний процесс (fork) для фонового снимка RDB или перезаписи AOF. Поскольку персистентность не может работать, данные не могут быть записаны на диск.","cause":"Операционная система не может выделить достаточно памяти для fork; обычно отключён overcommit_memory, и система отказывает, когда Redis пытается зарезервировать примерно копию используемой им памяти. Причиной также может быть нехватка физической/виртуальной памяти или лимиты числа процессов.","fix":"Включите overcommit памяти в Linux: sysctl vm.overcommit_memory=1 (добавьте в /etc/sysctl.conf, чтобы сделать постоянным). Добавьте памяти серверу или снизьте использование памяти Redis (maxmemory). Обеспечьте достаточный объём swap. Проверьте лимиты процессов/дескрипторов файлов (ulimit). Если персистентность критична, не выходите в продуктив без этих настроек.","tags":["fork","rdb","aof","persistence","overcommit","memory"]},{"code":"NOPROTO unsupported protocol version","title":"Unsupported RESP protocol version","severity":"caution","meaning":"Клиент запросил версию протокола RESP, которую сервер не поддерживает. Обычно возвращается, когда командой HELLO запрашивается RESP3 (версия 3), а сервер поддерживает только RESP2.","cause":"Отправлен HELLO 3, но на сервере работает старая версия Redis (до 6.0) или RESP3 не поддерживается; клиентская библиотека по умолчанию запрашивает RESP3, а сервер старый. Несоответствие версий.","fix":"Настройте клиент на использование RESP2 или запросите допустимую версию с помощью HELLO (HELLO 2). Обновите сервер до версии с поддержкой RESP3 (Redis 6+). Проверьте настройку версии протокола в клиентской библиотеке; большинство библиотек умеют автоматически переходить на RESP2. Проверьте версию сервера: 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":"Параметр конфигурации, который вы пытались изменить с помощью CONFIG SET, не распознан в этой версии Redis, либо неверно число аргументов. В Redis 7 полное сообщение — \"ERR Unknown option or number of arguments for CONFIG SET - '<name>'\"; старые версии возвращают \"ERR Unsupported CONFIG parameter\". CONFIG GET не выдаёт ошибку для неизвестного параметра; он возвращает пустой список.","cause":"Опечатка в имени параметра; использование параметра из другой версии Redis (параметры могут добавляться и удаляться между версиями); либо управляемый облачный сервис не позволяет изменять некоторые параметры. Типичная причина — запуск старых скриптов на более новых/старых версиях.","fix":"Проверьте допустимое имя параметра. Выведите текущие настройки по шаблону, например CONFIG GET 'maxmemory*'. Сверьтесь со списком параметров в документации для вашей версии Redis; версию можно увидеть с помощью INFO server. Если в облачном сервисе есть ограничения, используйте набор параметров, разрешённый провайдером. Обновите скрипты под целевую версию.","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 отправлен повторно, когда блок MULTI (транзакция) уже был открыт. Redis не поддерживает вложенные блоки транзакций; одновременно может быть активен только один MULTI.","cause":"Логика приложения начинает новый MULTI, не закрыв предыдущий через EXEC/DISCARD; повторное использование того же соединения из пула без корректного сброса состояния транзакции; либо обёртка библиотеки незаметно вызывает MULTI дважды.","fix":"Закрывайте каждый блок MULTI одним EXEC (или DISCARD для отмены) и открывайте новый блок только после завершения предыдущего. Чётко разделяйте начало и конец транзакции в потоке кода. Убедитесь, что перед возвратом соединения в пул не осталось открытой транзакции. Если нужна вложенная логика, рассмотрите выполнение атомарной операции с помощью Lua-скрипта (EVAL).","tags":["multi","transaction","nested","nested","exec","discard"]},{"code":"ERR Lua script attempted to access nonexistent global variable","title":"Lua script attempted to access a nonexistent global variable","severity":"caution","meaning":"Lua-скрипт, запущенный через EVAL/EVALSHA, попытался обратиться к неопределённой глобальной переменной. Чтобы защитить среду выполнения скриптов, Redis ограничивает создание/чтение глобальных переменных и возвращает такое обращение как ошибку.","cause":"Случайное использование глобальной переменной вместо локальной в скрипте (забыто ключевое слово local); обращение к неопределённому имени из-за опечатки; либо попытка хранить состояние в глобальной переменной. Изолированная среда Lua в Redis этого не допускает.","fix":"Объявляйте все переменные в скрипте через local: local x = ... Исправьте опечатки; убедитесь, что каждая переменная определена до использования. Скрипты должны быть без состояния; для постоянного состояния используйте ключи Redis (KEYS/redis.call), а не глобальные переменные Lua. Делайте скрипт коротким и детерминированным.","tags":["lua","eval","script","global","variable","local"]},{"code":"ERR unknown command","title":"Unknown command","severity":"caution","meaning":"Redis не распознаёт отправленную команду. Имя команды может быть неверным, команда может быть недоступна в этой версии либо отключена/переименована.","cause":"Опечатка в имени команды; команда добавлена в более новой версии Redis (её нет на старом сервере); вызов команды, требующей модуля (например, RedisJSON, RediSearch), без загруженного модуля; либо команда отключена через rename-command/ACL. Иногда из-за неверного разбиения аргументов клиентом имя команды тоже выглядит искажённым.","fix":"Проверьте имя и синтаксис команды (COMMAND DOCS command или документация). Проверьте версию сервера: INFO server. Для команды модуля убедитесь, что модуль загружен: MODULE LIST. Если команда скрыта через rename-command, проверьте конфигурацию. Убедитесь, что клиентская библиотека корректно кодирует команду.","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":"В Redis Cluster во время перемещения хеш-слота между узлами (rehashing/resharding) был выполнен запрос с несколькими ключами одного слота, причём часть ключей уже была перемещена, а часть — нет. Сервер просит повторить операцию чуть позже.","cause":"Выполнение многоключевой команды (MGET, MSET, многоключевой Lua, транзакция) во время миграции слота. В мигрирующем слоте часть ключей находится на исходном узле, а часть — на целевом, поэтому команду невозможно выполнить атомарно.","fix":"Перехватывайте эту ошибку в клиенте и повторяйте после короткой паузы (TRYAGAIN временная; она исчезает по завершении resharding). Для многоключевых операций гарантируйте нахождение всех ключей в одном слоте с помощью hash tag, например {app_user123}.profile, {app_user123}.settings. Выполняйте ребалансировку кластера (resharding) в периоды низкого трафика.","tags":["cluster","slot","rehash","resharding","tryagain","hash tag"]},{"code":"ERR DISCARD without MULTI","title":"DISCARD without MULTI","severity":"caution","meaning":"DISCARD отправлен, когда ни один блок MULTI не был открыт. DISCARD используется только для отмены выполняющейся транзакции; если отменять нечего, возвращается эта ошибка.","cause":"Вызов DISCARD без MULTI или повторно после того, как транзакция уже была закрыта через EXEC/DISCARD; безусловный вызов DISCARD в коде обработки ошибок; либо состояние соединения отличается от ожидаемого (на соединении, взятом из пула, MULTI не открыт).","fix":"Вызывайте DISCARD только тогда, когда MULTI действительно открыт; отслеживайте состояние транзакции в коде. В обработчиках ошибок проверяйте, открыта ли транзакция, прежде чем вызывать DISCARD. Используйте обёртку транзакций вашей библиотеки, чтобы она управляла состоянием автоматически. Гарантируйте чистое состояние в пуле соединений.","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":"Контекст поиска, используемый scroll-запросом, больше не существует на сервере. Обычно появляется при продолжении с сохранённым scroll_id после того, как контекст scroll истёк по тайм-ауту или был очищен.","cause":"Истёк keep-alive, указанный для scroll (слишком длинная пауза между запросами); контекст очищен вручную (clear scroll); либо контекст потерян при перезапуске узла. Контексты scroll хранятся в памяти узла, поэтому их время жизни ограничено.","fix":"Передавайте достаточный keep-alive в каждом запросе scroll (например, scroll=5m) и сокращайте задержку между страницами. В современном Elasticsearch для глубокой пагинации используйте search_after + Point in Time (PIT) вместо scroll; это более масштабируемый и рекомендуемый метод. Освобождайте контексты по завершении (clear scroll), чтобы не расходовать память впустую.","tags":["scroll","search_after","pit","context","timeout","pagination"]},{"code":"aliases_not_found_exception","title":"Aliases missing","severity":"safe","meaning":"Псевдоним (alias), с которым вы пытаетесь работать, не существует. Обычно возвращается при удалении несуществующего псевдонима или действии над ним.","cause":"Опечатка в имени псевдонима; псевдоним был удалён ранее; либо действие remove в API _aliases указывает псевдоним, который никогда не был привязан. Также появляется, когда ни один псевдоним не соответствует шаблону с подстановочными знаками.","fix":"Выведите существующие псевдонимы: GET _alias или для конкретного индекса GET my-index/_alias. Повторите операцию с правильным именем. Пишите скрипты удаления так, чтобы они не падали при отсутствии псевдонима; обрабатывайте действия условно в пакетных операциях _aliases. Чтобы атомарно перенаправить псевдонимы, используйте add и remove в одном запросе _aliases.","tags":["alias","alias name","not found","aliases","index","routing"]},{"code":"snapshot_missing_exception","title":"Snapshot is missing","severity":"caution","meaning":"Указанный снимок (snapshot) не найден в заданном репозитории. Это означает, что при запросе восстановления, удаления или состояния было использовано несуществующее имя снимка.","cause":"Опечатка в имени снимка; снимок был удалён ранее; запрошен не тот репозиторий; либо бэкап так и не завершился. Аналогичный результат возникает, когда определение репозитория изменилось или репозиторий недоступен.","fix":"Выведите снимки в репозитории: GET _snapshot/repo_name/_all (или для конкретного имени GET _snapshot/repo_name/snapshot_name). Повторите с правильным именем репозитория и снимка. Проверьте определение репозитория: GET _snapshot/repo_name. Убедитесь, что политика бэкапа (SLM) успешно выполняется и создаёт снимки.","tags":["snapshot","backup","repository","repo","restore","not found"]},{"code":"index_template_missing_exception","title":"Index template is missing","severity":"safe","meaning":"Указанный шаблон индекса не найден. При получении, удалении или выполнении операции на основе шаблона было использовано несуществующее имя шаблона.","cause":"Опечатка в имени шаблона; шаблон был удалён ранее; API устаревших (legacy) шаблонов перепутан с API составных шаблонов индексов (composable index template); либо шаблон так и не был создан. Также появляется, когда скрипт настройки не завершился.","fix":"Выведите существующие шаблоны. Для составных шаблонов: GET _index_template или GET _index_template/name. Для устаревших шаблонов: GET _template. Повторите с правильным API и именем. Если шаблона нет, создайте его: PUT _index_template/name {...}. Сделайте скрипты удаления/обновления идемпотентными, чтобы они не падали при отсутствии шаблона.","tags":["index template","template","composable","legacy","not found","mapping"]},{"code":"settings_exception","title":"Invalid settings","severity":"caution","meaning":"Операция отклонена, так как указанная настройка недопустима. При применении настроек индекса или кластера возникла проблема с именем настройки, её значением или возможностью её изменения в данном контексте.","cause":"Неизвестное/написанное с ошибкой имя настройки; значение неверного типа или вне допустимого диапазона; попытка изменить на работающем индексе статическую настройку, задаваемую только при создании индекса (например, number_of_shards); либо использование удалённой/переименованной настройки.","fix":"Сверьте имя и значение настройки с документацией. Обновляйте динамические настройки на работающем индексе: PUT my-index/_settings {\"index.number_of_replicas\": 2}. Статические настройки (например, number_of_shards) задаются только при создании; для их изменения требуется reindex. Посмотрите текущие настройки: GET my-index/_settings.","tags":["settings","setting","invalid","static","dynamic","reindex"]},{"code":"shard_not_found_exception","title":"Shard not found","severity":"danger","meaning":"Целевой шард не найден. Elasticsearch не смог выполнить запрос, так как не смог обратиться к соответствующему шарду указанного индекса.","cause":"Шард ещё не назначен (unassigned) или находится в процессе восстановления; шард временно отсутствует, так как узел покинул кластер; либо низкоуровневый запрос, направленный непосредственно к шарду, содержит уже недействительную информацию маршрутизации. Появляется при ухудшении состояния кластера.","fix":"Проверьте состояние кластера и шардов: GET _cluster/health и GET _cat/shards?v. Выясните, почему шарды не назначены: GET _cluster/allocation/explain. Если узел потерян, верните его; проверьте настройки диска/размещения (watermark). Если идёт восстановление, дождитесь его завершения. Поддерживайте достаточное число реплик, чтобы данные оставались доступны при потере одного узла.","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 не смог распознать тело запроса как корректное структурированное содержимое (XContent, например JSON). Отправленные данные не в ожидаемом формате или повреждены.","cause":"Тело запроса — некорректный JSON; неверный или отсутствующий заголовок Content-Type (не отправлен application/json); тело случайно отправлено в сжатом/двоичном виде; либо обычный JSON отправлен в конечные точки вроде _bulk, ожидающие JSON с разделением по строкам (NDJSON). Ошибки экранирования/кодировки также вызывают эту ошибку.","fix":"Убедитесь, что тело запроса — корректный JSON, и отправляйте заголовок Content-Type: application/json. Для конечной точки _bulk используйте NDJSON, где каждая строка — отдельный объект JSON, а последняя строка завершается символом новой строки (Content-Type: application/x-ndjson). Убедитесь, что клиентская библиотека корректно сериализует тело. Если используете сжатие, отправляйте соответствующий заголовок Content-Encoding.","tags":["json","xcontent","content-type","bulk","ndjson","request body"]},{"code":"document_source_missing_exception","title":"Document source is missing","severity":"caution","meaning":"Операции нужно поле _source документа, но оно не хранится. Операция, требующая _source (например, частичное обновление, получение исходного документа), была выполнена над индексом с отключённым source.","cause":"В маппинге индекса задано _source: {enabled: false}; поскольку исходный JSON документа не хранится, частичные обновления, reindex и запросы, возвращающие _source, не могут работать. Эту проблему вызывают конфигурации, отключающие source ради экономии места.","fix":"Для операций, которым нужен _source, source должен быть включён в маппинге. При создании нового индекса оставляйте _source включённым (он включён по умолчанию). Если _source отключён в существующем индексе, вместо обновления переиндексируйте документ целиком (перезапишите через index API). Для экономии места рассмотрите фильтрацию _source (includes/excludes) или synthetic _source вместо полного отключения source.","tags":["_source","source","update","mapping","reindex","document source"]}]},"errors/oracle-more3.json":{"engineId":"oracle","engine":"Oracle","order":1,"errors":[{"code":"ORA-00845","title":"MEMORY_TARGET not supported on this system","severity":"danger","meaning":"Экземпляр не может запуститься, так как автоматическое управление памятью, запрошенное через MEMORY_TARGET / MEMORY_MAX_TARGET, не поддерживается конфигурацией общей памяти операционной системы. В Linux обычно это происходит потому, что /dev/shm меньше запрошенного целевого объёма памяти.","cause":"MEMORY_TARGET задан больше, чем /dev/shm (смонтированный как tmpfs); либо MEMORY_TARGET (AMM) используется при включённых HugePages; AMM несовместим с HugePages, а ASMM (SGA_TARGET) совместим. Автоматическое управление памятью Oracle работает путём создания файлов в /dev/shm, и если этого пространства недостаточно, экземпляр не запускается.","fix":"Сделайте /dev/shm больше, чем MEMORY_TARGET: перемонтируйте tmpfs (mount -o remount,size=8G /dev/shm) и сделайте изменение постоянным в /etc/fstab. В качестве альтернативы используйте раздельные настройки вместо AMM: задайте MEMORY_TARGET и MEMORY_MAX_TARGET равными 0 и определите SGA_TARGET и PGA_AGGREGATE_TARGET. Если используете HugePages, отключите AMM и предпочтите ASMM (SGA_TARGET).","tags":["memory_target","memory","amm","dev/shm","hugepages","startup"]},{"code":"ORA-27102","title":"out of memory","severity":"danger","meaning":"При запуске экземпляр Oracle не смог выделить сегмент общей памяти (SGA), запрошенный у операционной системы. Обычно встречается вместе с ORA-27102 и кодом ошибки более низкого уровня OSD/Linux (например, Linux-x86_64 Error: 28: No space left on device).","cause":"Запрошенная для SGA память превышает лимиты общей памяти ядра (shmmax, shmall); на сервере недостаточно физической RAM/swap; либо конфигурация HugePages отсутствует/недостаточна, поэтому большую SGA невозможно выделить. Часто встречается после увеличения SGA_MAX_SIZE.","fix":"Увеличьте параметры общей памяти ядра: задайте kernel.shmmax и kernel.shmall в /etc/sysctl.conf так, чтобы они покрывали SGA, и выполните sysctl -p. Если используете HugePages, рассчитайте и задайте vm.nr_hugepages исходя из SGA. Проверьте общий объём RAM и swap на сервере; при необходимости уменьшите 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":"Серверный процесс не смог выделить место в своей PGA (Program Global Area), т. е. в частной памяти процесса. В отличие от общей памяти в SGA, это исчерпание собственной памяти отдельного сеанса/процесса.","cause":"Память процесса исчерпана очень большими операциями сортировки/хеширования, чрезмерно большими коллекциями PL/SQL, переменными, накапливающимися в неконтролируемых циклах, или лимитами памяти процесса в операционной системе (ulimit -v/-d, vm.max_map_count, адресное пространство). На 32-битных экземплярах причиной также может быть лимит адресного пространства.","fix":"Найдите, что потребляет память, изучив категорию выделения во второй строке ошибки (например, 'pga heap', 'koh-kghu sessi'). Проверьте PGA_AGGREGATE_LIMIT / PGA_AGGREGATE_TARGET. Проверьте лимиты операционной системы (ulimit -a; memlock, as, stack для пользователя oracle). Оптимизируйте запрос/PL/SQL и уменьшите размер пакетов массовой обработки. При необходимости обратитесь в Oracle Support с трассировочным файлом.","tags":["pga","process memory","memory","ulimit","sort","plsql"]},{"code":"ORA-12537","title":"TNS:connection closed","severity":"danger","meaning":"TNS-соединение между клиентом и сервером завершилось, так как канал связи был неожиданно закрыт. Сеанс обрывается во время установления соединения или сразу после него.","cause":"Listener передал клиента серверному процессу, но процесс не удалось создать или он сразу завершился: аварийный сбой экземпляра, достигнут лимит PROCESSES, нехватка ресурсов операционной системы, межсетевой экран/промежуточный уровень разрывает соединение либо правило TCP.VALIDNODE_CHECKING/TCP.INVITED_NODES в sqlnet.ora отклоняет клиента. Иногда соединение разрывает межсетевой экран между клиентом и сервером из-за простоя.","fix":"Изучите listener.log и sqlnet.log. Проверьте лимиты PROCESSES/SESSIONS: SELECT * FROM v$resource_limit WHERE resource_name IN ('processes','sessions'); Убедитесь, что списки TCP.INVITED_NODES/TCP.EXCLUDED_NODES в серверном sqlnet.ora не отклоняют IP клиента (несоответствие версии входа вызывает ORA-28040, а не эту ошибку). Увеличьте тайм-аут простоя на межсетевом экране или отправляйте keepalive с помощью SQLNET.EXPIRE_TIME. Проверьте alert.log на наличие записей об аварийном сбое экземпляра.","tags":["tns","connection","listener","processes","firewall","sqlnet"]},{"code":"ORA-03135","title":"connection lost contact","severity":"danger","meaning":"Ранее установленный рабочий сеанс потерял связь с сервером. Долгий запрос или простаивающее соединение было разорвано, так как стороны больше не могли связаться друг с другом.","cause":"Серверный процесс неожиданно завершился (ORA-00600/07445, kill, лимит ресурсов); профиль RESOURCE_LIMIT завершил сеанс; простаивающее соединение разорвано межсетевым экраном/балансировщиком нагрузки; либо произошёл сбой сети/потеря пакетов. Обычно возникает при запросах, долго не отвечающих, или при простаивающих соединениях.","fix":"Найдите в alert.log и соответствующих трассировочных файлах причину завершения процесса (ORA-00600/ORA-07445?). На стороне сети увеличьте тайм-аут простоя межсетевого экрана и включите обнаружение мёртвых соединений с помощью SQLNET.EXPIRE_TIME в sqlnet.ora. Проверьте лимиты CONNECT_TIME/IDLE_TIME в профиле пользователя. Изучите нагрузку на ресурсы сервера (память, CPU, PROCESSES).","tags":["connection","sqlnet","idle","firewall","dead connection","network"]},{"code":"ORA-01033","title":"ORACLE initialization or shutdown in progress","severity":"danger","meaning":"Экземпляр базы данных, к которому выполняется подключение, находится в процессе запуска (mount/open) или остановки. В этом промежуточном состоянии обычные пользовательские подключения не принимаются.","cause":"Экземпляр застрял в NOMOUNT/MOUNT (например, ожидает восстановления носителя), выполняется SHUTDOWN, либо база данных зависла до достижения OPEN из-за ошибки. Подключение к только что запущенному экземпляру до завершения запуска также возвращает эту ошибку.","fix":"Подключитесь как SYSDBA и проверьте состояние: sqlplus / as sysdba; затем SELECT status FROM v$instance; и SELECT open_mode FROM v$database; Попробуйте открыть экземпляр: ALTER DATABASE OPEN; Если требуется восстановление, выполните RECOVER DATABASE; затем откройте базу. Найдите в alert.log исходную ошибку, препятствующую открытию (отсутствующий/повреждённый файл данных, redo). Если остановка зависла, попробуйте SHUTDOWN ABORT, а затем STARTUP.","tags":["startup","shutdown","mount","open","recovery","instance"]},{"code":"ORA-00600","title":"internal error code, arguments: [...]","severity":"danger","meaning":"Не прошла внутренняя проверка согласованности в ядре Oracle. Это общая внутренняя ошибка, означающая, что код ядра столкнулся с неожиданным условием; аргументы в квадратных скобках указывают, какой путь выполнения кода был задействован.","cause":"Может быть вызвана повреждением блоков/данных, неисправленной ошибкой ПО Oracle, несогласованными уровнями патчей, проблемами с памятью/оборудованием или неожиданными граничными случаями. Первый аргумент (например, [ktbdchk1]) указывает, к какому уровню относится проблема, и является ключевым для анализа поддержкой.","fix":"Эту ошибку почти всегда следует решать с Oracle Support. Сохраните созданный трассировочный файл и запись в alert.log; найдите первый аргумент в My Oracle Support с помощью ORA-600 Troubleshooter и 'ORA-600 Lookup Tool'. Примените соответствующий патч/PSU/RU. Если подозревается повреждение, проверьте целостность блоков с помощью DBVERIFY/RMAN VALIDATE. В критичных средах не экспериментируйте в продуктиве, не открыв SR.","tags":["internal","ora-600","kernel","bug","trace","support"]},{"code":"ORA-07445","title":"exception encountered: core dump","severity":"danger","meaning":"Серверный процесс Oracle неожиданно аварийно завершился после получения исключения уровня операционной системы (например, SIGSEGV segmentation fault, access violation) и создал дамп памяти (core dump). В отличие от ORA-00600, ошибка вызвана сигналом от операционной системы.","cause":"Обычно это ошибка ПО Oracle, повреждение памяти, повреждённые блоки данных/индексов, несовместимые библиотеки или отказ оборудования/памяти. Имя функции и тип сигнала (например, SIGSEGV) в сообщении указывают путь выполнения кода, на котором произошёл сбой.","fix":"Сохраните созданные трассировочные файлы и файлы инцидентов, а также alert.log, и выполните поиск в My Oracle Support по имени функции + сигналу; обычно требуется патч. Проведите тесты оборудования/памяти и выровняйте уровни патчей операционной системы и Oracle. Если подозревается повреждение, выполните RMAN VALIDATE / DBVERIFY. При повторении откройте SR и отправьте дамп памяти в поддержку.","tags":["ora-7445","core dump","sigsegv","kernel","bug","trace"]},{"code":"ORA-01102","title":"cannot mount database in EXCLUSIVE mode","severity":"danger","meaning":"Базу данных невозможно смонтировать в режиме EXCLUSIVE, так как другой экземпляр (или оставшиеся файлы старого экземпляра) всё ещё, по-видимому, удерживает на ней блокировку. Экземпляр останавливается на этапе MOUNT.","cause":"Предыдущий экземпляр не был корректно остановлен, и остались файлы блокировки (lk<SID> в $ORACLE_HOME/dbs или остатки общей памяти/семафоров); к той же базе данных уже подключён другой экземпляр; либо в установке без RAC запущен второй экземпляр. Наиболее частые причины — оставшиеся процессы/общая память предыдущего экземпляра или устаревший файл lk<SID>.","fix":"Убедитесь, что никакой другой экземпляр фактически не работает: ps -ef | grep pmon и ipcs -m (сегменты общей памяти). Если остались следы, остановите экземпляр через ABORT и выполните очистку; при необходимости удалите оставшиеся сегменты/семафоры с помощью ipcrm. Удалите файл блокировки lk<SID> в $ORACLE_HOME/dbs (только если экземпляр точно остановлен). Затем попробуйте STARTUP MOUNT. В RAC проверьте CLUSTER_DATABASE и состояние узлов.","tags":["mount","exclusive","lock","startup","pmon","instance"]},{"code":"ORA-29913","title":"error in executing ODCIEXTTABLEOPEN callout","severity":"caution","meaning":"При обращении к внешней таблице произошла ошибка чтения/разбора исходного файла. Oracle потерпел неудачу в подпрограмме драйвера доступа (ORACLE_LOADER / ORACLE_DATAPUMP), которую вызывает для получения данных внешней таблицы.","cause":"Обычно файла нет по пути, на который указывает объект DIRECTORY, у пользователя ОС Oracle нет права на чтение файла/каталога, нет права на запись для файлов log/bad, либо неверны параметры доступа (разделитель полей, формат записей). Часто сопровождается подошибками, такими как ORA-29400 и KUP-04xxx.","fix":"Прочитайте подошибки (KUP-xxxx), чтобы определить первопричину. Проверьте путь DIRECTORY и наличие файла: SELECT directory_name, directory_path FROM all_directories; Предоставьте пользователю ОС Oracle право на чтение файла и каталога и право на запись в каталог log/bad. Приведите раздел ACCESS PARAMETERS определения внешней таблицы в соответствие с форматом исходного файла. При необходимости проверьте привилегии READ/WRITE на 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":"Клиенту не удалось установить соединение с сервером PostgreSQL. Соединение не удалось на уровне сети/адреса, ещё до того как стало сеансом.","cause":"Неверный хост/порт; адрес, на котором сервер не слушает (удалённое подключение при listen_addresses = 'localhost'); сервер остановлен/недоступен; межсетевой экран блокирует порт (по умолчанию 5432); либо ошибка разрешения DNS. Причиной также может быть неправильно настроенный пул соединений.","fix":"Убедитесь, что сервер работает и слушает правильный адрес: проверьте listen_addresses = '*' (или соответствующий IP) и параметр port в postgresql.conf, затем перезапустите сервер (listen_addresses и port требуют перезапуска; reload недостаточно). Проверьте доступ с клиента: psql -h host -p 5432 -U app_user. Откройте 5432/tcp в межсетевом экране. Проверьте значения host/port/DNS и строку подключения.","tags":["connection","connection","network","listen_addresses","port","firewall"]},{"code":"08004","title":"sqlserver_rejected_establishment_of_sqlconnection","severity":"danger","meaning":"Сервер доступен, но отказался устанавливать соединение. Сетевой доступ есть, но сервер не принял это соединение согласно политике.","cause":"Сервер PostgreSQL редко выдаёт этот код; чаще его сообщает клиентский драйвер или пул соединений. Фактические коды сервера для подобных отказов другие: отказ pg_hba.conf ('no pg_hba.conf entry for host', метод reject) возвращает 28000 (invalid_authorization_specification), исчерпание max_connections — 53300 (too_many_connections), а отказ в соединении во время запуска/остановки — 57P03.","fix":"Прочитайте в журнале сервера реальный SQLSTATE и причину отказа. При отказе pg_hba (28000) добавьте подходящую строку для IP клиента, пользователя и базы данных (например, host all all 10.0.0.0/24 scram-sha-256) и перечитайте конфигурацию с помощью SELECT pg_reload_conf();. При насыщении соединений (53300) сравните число клиентских соединений с max_connections с помощью 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":"Ваше активное соединение было завершено из-за команды администратора. Клиент получает эту ошибку, когда сервер останавливается или сеанс завершается вручную ('terminating connection due to administrator command').","cause":"DBA завершил сеанс с помощью pg_terminate_backend(); сервер останавливается в режиме fast/immediate (режим smart не завершает сеансы, а ждёт их окончания); либо существующие соединения были разорваны для изменения конфигурации/планового обслуживания. В некоторых сценариях отработки отказа бывший primary тоже разрывает соединения таким образом.","fix":"Обычно это не постоянная ошибка; приложение должно переподключиться (повторить попытку). Если она неожиданна, выясните причину остановки/завершения в журнале сервера. Для планового обслуживания реализуйте в приложении автоматическое переподключение и проверки работоспособности пула соединений. Журнал сервера не показывает, кто завершил сеанс; чтобы отслеживать вызовы pg_terminate_backend, ведите журнал аудита с помощью log_statement (или pgaudit).","tags":["shutdown","terminate","connection","maintenance","failover","retry"]},{"code":"53400","title":"configuration_limit_exceeded","severity":"danger","meaning":"Операция завершилась ошибкой, так как превышен лимит конфигурации. Сервер отклонил запрос из-за заданного лимита (например, temp_file_limit, число слотов репликации или WAL senders).","cause":"Типичные примеры: использование временных файлов запросом превышает temp_file_limit ('temporary file size exceeds temp_file_limit'); все слоты репликации заняты ('all replication slots are in use', max_replication_slots); превышен лимит max_wal_senders. Примечание: исчерпание max_prepared_transactions возвращает 53200, а отключённые подготовленные транзакции (значение 0) — 55000.","fix":"В сообщении об ошибке указано, какой лимит превышен. Для temp_file_limit оптимизируйте запрос (с помощью EXPLAIN ANALYZE найдите шаги сортировки/хеширования, сбрасываемые на диск), при необходимости увеличьте work_mem на уровне сеанса или осознанно повысьте лимит. Если слоты исчерпаны, удалите неиспользуемые слоты, предварительно проверив их владельцев: SELECT slot_name, slot_type, active FROM pg_replication_slots; или увеличьте max_replication_slots / max_wal_senders и перезапустите сервер.","tags":["limit","temp_file_limit","replication slot","max_wal_senders","configuration","resource"]},{"code":"XX001","title":"data_corrupted","severity":"danger","meaning":"PostgreSQL обнаружил повреждение данных на странице таблицы/heap. Запрос не может безопасно завершиться, так как содержимое прочитанного блока несогласованно ('invalid page in block').","cause":"Отказ диска/хранилища, повреждение файловой системы, несоблюдение гарантий fsync при внезапной потере питания, неисправное оборудование (RAM/контроллер) или, изредка, программная ошибка. При включённых контрольных суммах повреждение выявляется раньше.","fix":"Сначала проверьте оборудование/хранилище и, если возможно, сразу запланируйте возврат к физическому бэкапу (PITR). Если есть исправный бэкап, восстановите его. Для восстановления, С РИСКОМ ДЛЯ ДАННЫХ и только в крайнем случае, можно обнулить повреждённые страницы с помощью zero_damaged_pages = on и выгрузить читаемые данные (это приводит к потере данных). Включите data_checksums (в существующем кластере с PG12+ — pg_checksums --enable при остановленном сервере), проведите тесты RAM и делайте регулярные PITR-бэкапы.","tags":["corruption","corruption","checksum","page","hardware","pitr"]},{"code":"XX002","title":"index_corrupted","severity":"danger","meaning":"Обнаружено повреждение структуры индекса. Поскольку данные, на которые указывает индекс, не согласуются с фактическими строками в heap, запросы, использующие индекс, могут возвращать неверные результаты или завершаться ошибкой.","cause":"Отказ оборудования/хранилища, нарушение порядка в B-дереве из-за изменения правил сортировки (collation) после обновления glibc/ICU операционной системы, либо внезапный сбой. Особенно обновления collation операционной системы вызывают незаметное повреждение текстовых индексов.","fix":"Перестройте повреждённый индекс: REINDEX INDEX idx_name; или REINDEX TABLE my_table; На больших таблицах используйте REINDEX INDEX CONCURRENTLY, чтобы уменьшить блокировки. Если причина в collation, перестройте все затронутые текстовые индексы и проверьте их с помощью расширения amcheck (bt_index_check). Если первопричина в оборудовании, замените хранилище.","tags":["index","index","corruption","reindex","collation","amcheck"]},{"code":"25001","title":"active_sql_transaction","severity":"caution","meaning":"Команда, которая может выполняться только вне транзакции или в её начале, была вызвана при активной транзакции. Некоторые команды не выполняются внутри блока транзакции.","cause":"Настройки, такие как SET TRANSACTION ISOLATION LEVEL, должны задаваться до первого запроса в блоке ('SET TRANSACTION ISOLATION LEVEL must be called before any query'). Аналогично некоторые команды отклоняются внутри открытого BEGIN/транзакции. Обычно это происходит, когда приложение отключает autocommit и незаметно начинает транзакцию.","fix":"Задавайте настройки уровня транзакции в самом её начале, до выполнения любого запроса: BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ... Проверьте, не открывает ли клиентская библиотека неявную транзакцию (настройка autocommit). Для команд, которые не могут выполняться внутри блока транзакции, сначала завершите текущую транзакцию через COMMIT/ROLLBACK.","tags":["transaction","transaction","isolation","set transaction","autocommit","begin"]},{"code":"42P16","title":"invalid_table_definition","severity":"caution","meaning":"Команда DDL отклонена, так как определение таблицы недопустимо. Запрошенная структура таблицы/столбцов нарушает правила PostgreSQL.","cause":"Ситуации вроде определения более одного первичного ключа ('multiple primary keys for table are not allowed'), определений, конфликтующих с наследованием, ограничений столбцов generated/identity или изменения в структуру, которую ALTER TABLE не может создать. Часто встречается в автоматически сгенерированных скриптах миграций.","fix":"Прочитайте конкретную причину в сообщении об ошибке. Определяйте только один PRIMARY KEY; если нужен многостолбцовый ключ, используйте составной ключ вида PRIMARY KEY (a, b). Изучите правила для столбцов identity/generated. Проверьте текущее определение таблицы с помощью \\d my_table и исправьте скрипт миграции.","tags":["ddl","table","primary key","definition","alter table","migration"]},{"code":"P0001","title":"raise_exception","severity":"caution","meaning":"Функция или триггер PL/pgSQL намеренно вызвали ошибку с помощью RAISE EXCEPTION. Эту ошибку выдаёт не ядро базы данных, а код бизнес-правил, написанный вами (или библиотекой).","cause":"Когда бизнес-логика не проходит проверку, разработчик вызвал RAISE EXCEPTION 'message' USING ERRCODE = ...;. Например, триггер выдаёт пользовательскую ошибку, чтобы заблокировать недопустимое обновление или обеспечить правило согласованности.","fix":"Сообщение об ошибке и поля HINT/DETAIL (если есть) объясняют, что отклонила функция; выполните бизнес-правило соответствующим образом. Чтобы найти источник, изучите код соответствующей функции/триггера (операторы RAISE). Если нужен конкретный SQLSTATE, назначьте осмысленный код через RAISE ... USING ERRCODE вместо общего P0001, чтобы приложение могло его различать.","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":"База данных (схема), к которой вы пытаетесь подключиться или которую используете, не существует на сервере. Оператор USE или имя базы данных по умолчанию в строке подключения указывает на несуществующую схему.","cause":"База данных так и не была создана, её имя написано с ошибкой (чувствительность к регистру может зависеть от операционной системы), она была удалена, либо приложение подключается к неверной среде (dev/prod). Ещё одна частая причина — схемы не перенесены при переходе на новый сервер.","fix":"Выведите существующие базы данных: SHOW DATABASES; Убедитесь, что имя написано правильно и регистр совпадает (важна настройка lower_case_table_names). Если базы нет, создайте её: CREATE DATABASE database_name CHARACTER SET utf8mb4; или восстановите из бэкапа. Проверьте целевую среду в конфигурации подключения приложения.","tags":["database","database","schema","use","name","connection"]},{"code":"1364","title":"Field 'name' doesn't have a default value","severity":"caution","meaning":"Строка вставлена без значения для столбца, определённого как NOT NULL без DEFAULT. В строгом режиме SQL MySQL выдаёт ошибку, так как не может автоматически заполнить отсутствующий обязательный столбец.","cause":"Обязательный столбец (NOT NULL, без DEFAULT) пропущен в INSERT, а в sql_mode включён STRICT_TRANS_TABLES/STRICT_ALL_TABLES. Приложение, написанное для старого нестрогого режима, сталкивается с этой ошибкой при переносе на сервер со строгим режимом.","fix":"Добавьте в INSERT значение для отсутствующего столбца или определите для столбца разумный DEFAULT: ALTER TABLE my_table MODIFY my_column ... NOT NULL DEFAULT 'value'; Если столбец действительно необязательный, разрешите NULL. sql_mode можно ослабить как временный обходной путь/для совместимости, но для качества данных рекомендуется сохранять строгий режим.","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 исчерпала память, выделенную под удерживаемые блокировки (структуры блокировок внутри buffer pool). Это происходит, когда одна транзакция накапливает слишком много блокировок строк/промежутков (gap), и транзакция откатывается.","cause":"Очень большие одиночные транзакции: UPDATE/DELETE миллионов строк за раз, условие WHERE без индекса, блокирующее всю таблицу, или слишком малый для нагрузки innodb_buffer_pool_size. Классический триггер — крупные пакетные задания.","fix":"Разбейте большой DML на небольшие пакеты (например, удаляйте/обновляйте по 10,000 строк за раз с LIMIT в цикле и COMMIT после каждого пакета). Проиндексируйте столбцы WHERE, чтобы уменьшить число блокируемых строк. В качестве постоянного решения увеличьте innodb_buffer_pool_size (структуры блокировок выделяются из него). Избегайте неоправданно длинных транзакций.","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":"Сервер-реплика (slave) получил фатальную ошибку при чтении данных из двоичного журнала source (master), и репликация остановилась. Реплика не может найти ожидаемую позицию в binlog source.","cause":"Необходимые файлы двоичного журнала удалены на source (binlog_expire_logs_seconds — expire_logs_days до 8.0, удалён в 8.4 — или PURGE BINARY LOGS); пропуск/несоответствие GTID; повреждённая позиция binlog после аварийного сбоя source; либо реплика настолько отстала, что запрашивает журнал, которого больше нет. Типичное подсообщение — 'Could not find first log file name in binary log index file'.","fix":"Проверьте состояние репликации: SHOW REPLICA STATUS\\G (8.0.22+; в старых версиях SHOW SLAVE STATUS, старый синтаксис удалён в 8.4). Выведите binlog, доступные на source: SHOW BINARY LOGS; Если отсутствующий журнал удалён, реплику необходимо пересоздать из нового бэкапа/позиции GTID (mysqldump --source-data — --master-data до 8.0.26, устаревший псевдоним в 8.0.26+ — или плагин CLONE). Увеличьте хранение binlog на source в соответствии с нагрузкой; если используете GTID, проверьте пропуски GTID.","tags":["replication","replication","binlog","master","gtid","slave"]},{"code":"1129","title":"Host 'host' is blocked because of many connection errors","severity":"danger","meaning":"MySQL заблокировал клиентский хост после того, как число последовательных ошибок соединения с него превысило порог (max_connect_errors). Хост внесён в чёрный список из-за неудачных до этого момента рукопожатий (handshake).","cause":"Балансировщик нагрузки/инструмент мониторинга отправляет на TCP-порт незавершённые или некорректные рукопожатия; сетевые проблемы обрывают соединения на полпути; либо неправильно настроенный клиент продолжает неудачные попытки подключения. Когда счётчик ошибок достигает max_connect_errors, хост блокируется.","fix":"Немедленно снимите блокировку: в SQL — TRUNCATE TABLE performance_schema.host_cache; (8.0.23+; FLUSH HOSTS устарел в 8.0.23 и удалён в 8.4) или из командной строки — mysqladmin flush-hosts. Чтобы увидеть заблокированные хосты: SELECT host, ip, sum_connect_errors FROM performance_schema.host_cache; Устраните первопричину: настройте проверку работоспособности/инструмент мониторинга на выполнение полного рукопожатия MySQL и проверьте стабильность сети. В исправных средах можно повысить max_connect_errors для увеличения допуска, но главное — устранить фактические ошибки соединения.","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":"Таблица, обычно хранящаяся на движке MyISAM (в MariaDB также Aria), помечена как повреждённая/аварийная и не может использоваться, пока не будет восстановлена. Запросы к таблице завершаются этой ошибкой.","cause":"Таблица MyISAM не была корректно закрыта при внезапном аварийном сбое сервера/потере питания, заполнении диска, отказе оборудования или завершении процесса mysqld. Поскольку MyISAM не обеспечивает транзакций и устойчивости к сбоям, это случается гораздо чаще, чем с InnoDB.","fix":"Восстановите таблицу: REPAIR TABLE table_name; или в offline с помощью myisamchk -r. Перед восстановлением всегда делайте бэкап файлов данных. В качестве постоянного решения переведите таблицы на InnoDB: ALTER TABLE my_table ENGINE=InnoDB; что делает их устойчивыми к сбоям. Проверьте состояние диска и параметр myisam_recover_options.","tags":["myisam","crashed","repair","corruption","corruption","innodb"]},{"code":"1615","title":"Prepared statement needs to be re-prepared","severity":"caution","meaning":"Выполнение подготовленного на сервере оператора отклонено, так как он стал недействительным после изменения метаданных, от которых он зависит (определения таблицы/представления). Оператор необходимо подготовить заново.","cause":"DDL (ALTER TABLE) над базовой таблицей после подготовки оператора, пересоздание таблицы, переполнение кеша метаданных с вытеснением записи или слишком малые лимиты table_definition_cache/table_open_cache. Часто встречается в системах с интенсивным DDL и в некоторых сценариях ORM/пулов.","fix":"Большинство клиентских библиотек перехватывают эту ошибку и автоматически заново подготавливают оператор; в приложении перехватывайте эту ошибку и повторяйте prepare + execute. Если ошибка повторяется часто, увеличьте table_definition_cache и table_open_cache. Избегайте ненужного DDL во время трафика приложения.","tags":["prepared statement","re-prepare","ddl","metadata","cache","retry"]},{"code":"1553","title":"Cannot drop index needed in a foreign key constraint","severity":"caution","meaning":"Вы пытаетесь удалить индекс, необходимый для ограничения внешнего ключа. InnoDB не позволяет удалить индекс, на который опирается FK.","cause":"В InnoDB каждый внешний ключ требует индекса на ссылающемся столбце (столбцах). Удаление этого индекса через DROP INDEX отклоняется, так как ограничение FK осталось бы без опоры. Обычно возникает при реорганизации таблицы (очистке индексов).","fix":"Сначала удалите ограничение внешнего ключа, затем удалите индекс: ALTER TABLE my_table DROP FOREIGN KEY fk_name; затем DROP INDEX idx_name ON my_table; При необходимости пересоздайте FK так, чтобы он опирался на другой подходящий индекс. Чтобы вывести существующие FK: 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":"Вы попытались загрузить данные из файла на стороне клиента с помощью LOAD DATA LOCAL INFILE, но эта возможность отключена на стороне клиента и/или сервера. По соображениям безопасности LOCAL в MySQL 8 по умолчанию отключён.","cause":"Системная переменная local_infile на сервере имеет значение OFF и/или клиентский драйвер не включил LOCAL INFILE. Возможность по умолчанию ограничена, так как может позволить вредоносному серверу читать файлы клиента.","fix":"Если это действительно нужно, включите на обеих сторонах: на сервере SET GLOBAL local_infile = 1; на клиенте включите флаг LOCAL INFILE драйвера (например, параметр соединения allowLoadLocalInfile / --local-infile=1). С точки зрения безопасности предпочтительно разместить файл на сервере и использовать LOAD DATA INFILE без LOCAL либо инструменты массового INSERT/импорта. По завершении снова отключите local_infile.","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":"Определение таблицы недопустимо, так как нарушены правила AUTO_INCREMENT: в таблице может быть только один столбец с автоинкрементом, и он должен входить в ключ (PRIMARY KEY или UNIQUE/индекс).","cause":"Определение более одного столбца AUTO_INCREMENT или невключение столбца AUTO_INCREMENT ни в один ключ (индекс). Возникает при CREATE TABLE или ALTER TABLE.","fix":"Оставьте один столбец AUTO_INCREMENT и включите его в ключ: CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY, ...); Чтобы исправить существующую таблицу, сначала добавьте столбец в ключ: ALTER TABLE t ADD PRIMARY KEY (id); затем определите AUTO_INCREMENT. Если нужно несколько автостолбцов, пересмотрите проект; MySQL это не поддерживает.","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":"Синтаксический анализатор Transact-SQL обнаружил недопустимый синтаксис рядом с указанной лексемой и не смог скомпилировать оператор. Запрос отклоняется до выполнения.","cause":"Опечатка, пропущенная запятая/скобка, зарезервированное ключевое слово (например, USER, ORDER) без квадратных скобок, незакрытый строковый литерал или несовместимый синтаксис, скопированный из другого диалекта SQL. Частая причина — также ошибки конкатенации в динамическом SQL.","fix":"Внимательно изучите область вокруг указанной лексемы; ошибка часто находится в предыдущей строке. Заключайте зарезервированные слова в квадратные скобки ([Order]) и проверьте парность скобок и запятых. Если формируете динамический SQL, выведите сгенерированный текст через PRINT @sql и выполните его. Убедитесь, что синтаксис поддерживается версией/уровнем совместимости.","tags":["syntax","syntax error","parse","tsql","dynamic sql","keyword"]},{"code":"8114","title":"Error converting data type X to Y","severity":"caution","meaning":"Значение не удалось преобразовать из одного типа данных в другой, так как типы несовместимы. В отличие от ошибки 245, обычно это неудачное преобразование типа на уровне параметра/выражения (например, varchar в numeric).","cause":"Отправка нечислового или некорректного текста в числовой столбец/параметр; переполнение масштаба/точности; попытка преобразовать пустую строку ('') в numeric; либо передача аргумента неверного типа в параметризованном запросе. Часто встречается при неявных преобразованиях, генерируемых ORM.","fix":"Убедитесь, что преобразуемые значения соответствуют целевому типу. Для безопасного преобразования используйте TRY_CONVERT / TRY_CAST; при неудаче они возвращают NULL, поэтому такие строки можно отфильтровать через WHERE. Приведите типы параметров в соответствие с типами столбцов. Просканируйте данные с помощью ISNUMERIC/TRY_CAST, чтобы найти проблемные строки, и очистите пустые строки.","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 не смог выделить память, необходимую диспетчеру блокировок, и не может создать новые блокировки. Операция завершается ошибкой, так как пул памяти блокировок исчерпан.","cause":"Слишком много мелкозернистых блокировок (строк/страниц), удерживаемых одновременно: большой, неиндексированный или массовый DML; запрет эскалации блокировок; общая нехватка памяти на сервере; либо долго открытые транзакции. Изредка триггером является внешняя нагрузка на память.","fix":"Разбейте большие операции на меньшие пакеты, чтобы уменьшить число одновременных блокировок. Используйте подходящие индексы, чтобы сократить число сканируемых/блокируемых строк. Проверьте конфигурации, отключающие эскалацию блокировок: флаги трассировки 1211/1224 и ALTER TABLE ... SET (LOCK_ESCALATION = DISABLE); проверьте с помощью SELECT name, lock_escalation_desc FROM sys.tables; и DBCC TRACESTATUS(-1); Проверьте память сервера и параметр max server memory и снизьте нагрузку на память.","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":"Вы попытались изменить данные (INSERT/UPDATE/DELETE/DDL) в базе данных, помеченной как доступная только для чтения. SQL Server отклонил запись.","cause":"База данных переведена в READ_ONLY; соединение направлено на вторичную реплику в группе доступности (Availability Group), предназначенную для чтения; база данных осталась только для чтения после снимка или восстановления; либо файловая система доступна только для чтения. Типичная причина — приложение, случайно направленное на вторичную реплику.","fix":"Если запись действительно необходима: ALTER DATABASE mydb SET READ_WRITE; В Availability Group направьте приложение на первичную реплику (проверьте маршрутизацию соединений ReadWrite / ApplicationIntent). В снимок только для чтения писать нельзя; используйте вместо него исходную базу данных. Также проверьте права доступа к файлам и файловую систему.","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":"При добавлении нового пользователя в базу данных обнаружено, что пользователь с таким же именем в этой базе данных уже существует. Также появляется при сопоставлении имени входа с существующим пользователем (потерянные пользователи, orphaned users).","cause":"Обычно при восстановлении базы данных из бэкапа на другом сервере SID имён входа не совпадают с пользователями базы данных (потерянные пользователи). Попытка повторно добавить то же имя через CREATE USER или вручную настроить сопоставление имени входа и пользователя вызывает эту ошибку.","fix":"Повторно сопоставьте потерянного пользователя с существующим именем входа: ALTER USER app_user WITH LOGIN = login_name; (рекомендуется вместо устаревшей sp_change_users_login). Если пользователь действительно лишний: DROP USER app_user; Чтобы найти потерянных пользователей: 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":"Попытка входа с проверкой подлинности Windows отклонена, так как поступила из недоверенного домена (или проверка подлинности Windows использована там, где требуется проверка подлинности SQL). Сервер не доверяет этому удостоверению.","cause":"Клиент подключается с удостоверением Windows из домена/рабочей группы, которой SQL Server не доверяет; машины находятся в разных/недоверенных доменах; неверная конфигурация Kerberos/SPN; либо в строке подключения используется Integrated Security, когда ожидается проверка подлинности SQL. Попытка входа SQL при отключённом смешанном режиме даёт похожий симптом.","fix":"Если требуется проверка подлинности SQL, переведите сервер в смешанный режим (Mixed Mode) и используйте в строке подключения имя пользователя/пароль (Integrated Security=false). Если требуется проверка подлинности Windows, проверьте доверие доменов и конфигурацию DNS/Kerberos SPN. Убедитесь, что клиент и сервер находятся в одном или доверенном домене. Уточните причину по значению 'state' в журнале ошибок.","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":"При чтении страницы SQL Server обнаружил страницу, принадлежащую другому объекту, а не ожидаемому объекту/единице распределения. Это явный признак серьёзного физического/логического повреждения базы данных.","cause":"Нарушенные связи страниц из-за отказа хранилища/диска, ошибки контроллера или драйвера, повреждения кеша записи при внезапной потере питания или повреждения файловой системы. 'Transient 605' иногда может быть вызвана временными проблемами ввода-вывода, но устойчивые случаи — это реальное повреждение.","fix":"Немедленно выполните DBCC CHECKDB ('mydb') WITH NO_INFOMSGS, ALL_ERRORMSGS, чтобы определить масштаб повреждения. Если возможно, восстановите из заведомо исправного бэкапа (предпочтительно PITR, минимизирующее потерю данных). Протестируйте хранилище/оборудование и проверьте параметр PAGE_VERIFY CHECKSUM. Параметры REPAIR могут привести к потере данных, поэтому используйте их только в крайнем случае и после создания бэкапа; в критичных средах привлеките поддержку Microsoft.","tags":["corruption","database corruption","page","allocation unit","dbcc checkdb","storage"]},{"code":"4922","title":"ALTER TABLE ALTER COLUMN failed because one or more objects access this column","severity":"caution","meaning":"Вы попытались изменить тип/свойство столбца, но этому помешали объекты, зависящие от столбца (индекс, ограничение, вычисляемый столбец, статистика, представление с привязкой к схеме и т. д.). Столбец нельзя изменить, пока зависимости не будут устранены.","cause":"Столбец используется в индексе, ограничении PRIMARY KEY/UNIQUE/CHECK/DEFAULT, выражении вычисляемого столбца, представлении/функции, определённых WITH SCHEMABINDING, или объекте статистики. Изменение типа/размера сделало бы эти объекты недействительными, поэтому ALTER отклоняется.","fix":"Сначала найдите зависимые объекты и временно удалите или отключите их, измените столбец, затем создайте их заново. Чтобы увидеть зависимости, выполните запросы к sys.sql_expression_dependencies и sys.indexes/sys.default_constraints. Типичный порядок: DROP CONSTRAINT/INDEX -> ALTER COLUMN -> повторный CREATE. Для представлений с привязкой к схеме временно уберите SCHEMABINDING и затем верните его.","tags":["alter column","dependency","index","constraint","schemabinding","ddl"]},{"code":"7202","title":"Could not find server 'name' in sys.servers","severity":"caution","meaning":"Имя сервера, указанное в запросе через связанный сервер (linked server) или четырёхчастное имя (server.database.schema.object), не найдено среди настроенных связанных серверов. SQL Server не смог разрешить имя.","cause":"Связанный сервер так и не был определён; имя написано с ошибкой; связанный сервер, существующий в другой среде (dev/prod), отсутствует в продуктиве; либо OPENQUERY/четырёхчастное имя указывает на неверное имя сервера. Часто встречается при переносе скриптов между серверами.","fix":"Выведите определённые связанные серверы: SELECT name FROM sys.servers; или EXEC sp_linkedservers. При необходимости добавьте: EXEC sp_addlinkedserver @server='SERVER', ...; и настройте сопоставление имён входа с помощью sp_addlinkedsrvlogin. Убедитесь, что четырёхчастное имя в запросе использует правильное имя связанного сервера; если обращаетесь только к локальному объекту, уберите префикс сервера.","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":"В Azure SQL Database служба временно отклонила запрос из-за нехватки ресурсов (регулирование ядра, throttling). Система занята, и клиент должен вскоре повторить попытку.","cause":"Такой ресурс, как CPU, соединения, скорость записи журнала транзакций или число рабочих потоков, достиг лимита выбранного уровня обслуживания (DTU/vCore); внезапный всплеск нагрузки; либо долгие/неэффективные запросы, потребляющие ресурс. Сопровождающий ошибку 'reason code' показывает, какой ресурс регулируется.","fix":"Реализуйте в приложении логику повтора с экспоненциальной задержкой; это ожидаемое поведение для временных ошибок. Проверьте использование ресурсов: sys.dm_db_resource_stats и метрики портала Azure. Оптимизируйте запросы/индексы и снизьте ненужный параллелизм. Если насыщение постоянное, повысьте уровень обслуживания (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":"Запись (или другая операция, требующая primary) отправлена члену набора реплик, который в данный момент не является primary. Запись принимает только primary, поэтому операция отклонена.","cause":"Приложение пишет на secondary; во время отработки отказа (выборов) бывший primary стал secondary, а клиент всё ещё подключён к нему; либо предпочтения чтения/записи драйвера и сведения о топологии устарели. Типичная причина — отсутствие параметра replicaSet в строке подключения.","fix":"Настройте драйвер со всеми членами набора реплик и именем набора реплик (replicaSet=... и все хосты в строке подключения), чтобы драйвер автоматически обнаруживал primary и перенаправлял запросы при отработке отказа. Используйте предпочтения чтения/записи по умолчанию, направляющие запись на primary. Поскольку это временная ошибка, включите в приложении retryable writes. Проверьте, какой член является PRIMARY, с помощью 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":"Операция не могла завершиться, так как сервер (mongod/mongos) останавливается. Останавливающийся узел отклоняет новые запросы и завершает выполняющиеся операции.","cause":"Плановый перезапуск/обслуживание, остановка перед stepdown primary в наборе реплик, сигнал завершения, отправленный контейнером/оркестратором, или остановка процесса из-за исчерпания ресурсов. Ожидаема во время поэтапных (rolling) обновлений.","fix":"Обычно это временная ошибка; используйте в приложении логику повтора и подключение к набору реплик, чтобы запросы направлялись к другому исправному члену. Если она неожиданна, проверьте журналы mongod на причину остановки (сигнал, OOM killer, ресурсы). При поэтапном обслуживании корректно понижайте primary перед остановкой (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":"Primary сложил с себя роль во время выполнения операции и больше не принимает запись. Поэтому выполнявшаяся команда была прервана.","cause":"В наборе реплик были инициированы выборы: primary вручную понижен для обслуживания, потерял связь с большинством, изменился приоритет или произошло разделение сети. Операции, выполняющиеся в момент отработки отказа, получают эту ошибку.","fix":"Обычно это ошибка, допускающая повтор; при включённых в драйвере retryable writes/reads операция может быть автоматически направлена к новому primary. Перехватывайте эту ошибку в приложении и повторяйте. Если она возникает часто, изучите состояние набора реплик: проверьте состояния членов, сетевую задержку и отставание oplog с помощью rs.status() и сократите лишние выборы (настройки heartbeat/тайм-аутов).","tags":["stepdown","election","failover","primary","replica set","retry"]},{"code":"24 LockTimeout","title":"Operation exceeded time limit waiting for a lock","severity":"caution","meaning":"Операция превысила лимит времени при ожидании необходимой блокировки (например, на уровне базы данных/коллекции) и была прервана. Блокировку не удалось получить вовремя.","cause":"Долгая операция или команда DDL (например, конфликтующие административные операции над коллекцией) удерживает необходимую блокировку; интенсивная параллельная запись; либо операция, ограниченная maxTimeMS, истекает при ожидании блокировки. Встречается при конфликтующих операциях с индексами/коллекциями.","fix":"Найдите операцию, удерживающую блокировку: изучите активные и ожидающие операции с помощью db.currentOp(); при необходимости завершите её с помощью db.killOp(). Перенесите конфликтующие административные команды (построение индексов, collMod, compact) на часы низкой нагрузки. Делайте операции короткими и избегайте неоправданно длинных транзакций. При необходимости настройте maxTimeMS под нагрузку.","tags":["lock","lock wait","timeout","currentop","ddl","concurrency"]},{"code":"7 HostNotFound","title":"Could not find host","severity":"danger","meaning":"MongoDB не смогла разрешить/найти имя хоста, к которому пыталась подключиться. Драйвер или член набора реплик не может достичь указанного хоста, поэтому соединение невозможно установить.","cause":"Ошибка разрешения DNS, неверное/устаревшее имя хоста в строке подключения, имена хостов членов набора реплик (rs.conf), которые клиент не может разрешить, либо проблема сети/доступа. В контейнерных средах типичная причина — внутренние имена хостов, не разрешаемые извне.","fix":"Убедитесь, что имя хоста правильное и разрешается (проверьте DNS с помощью ping/nslookup). Обновите хосты в строке подключения. Убедитесь, что имена членов набора реплик разрешаются и клиентами, и членами; при необходимости замените имена хостов в rs.conf() на доступные FQDN/IP. Если используете SRV (mongodb+srv), проверьте DNS-записи SRV.","tags":["host","dns","connection","replica set","network","srv"]},{"code":"175 QueryPlanKilled","title":"Query plan killed during execution","severity":"caution","meaning":"План выполняющегося запроса стал недействительным, так как базовые данные/коллекция изменились, и он был завершён. Запрос прерван, поскольку не мог безопасно продолжаться.","cause":"Во время выполнения запроса (особенно с открытым курсором) коллекция была удалена, используемый индекс удалён либо произошло переименование/перенастройка. Типичный триггер — изменения базовых объектов во время долгих вызовов getMore.","fix":"Перезапустите запрос/курсор (повторите). Убедитесь, что во время выполнения запросов над коллекцией или индексом не выполняется разрушающий DDL (drop, dropIndex, rename); планируйте такое обслуживание вне трафика запросов. Сокращайте долгие чтения и делайте необходимые индексы постоянными.","tags":["query","cursor","getmore","drop","index","plan"]},{"code":"11602 InterruptedDueToReplStateChange","title":"Operation interrupted due to a replication state change","severity":"danger","meaning":"Операция прервана, так как изменилось состояние набора реплик (например, сменился primary). Выполнявшаяся команда завершена, поскольку не могла безопасно завершиться в новой топологии.","cause":"Выполняющиеся операции прерываются во время выборов, stepdown primary, смены ролей членов или изменения конфигурации. Имеет ту же причину, что и 189 PrimarySteppedDown, и обычно появляется в момент отработки отказа.","fix":"Это ошибка, допускающая повтор; при включённых в драйвере retryable writes/reads операцию можно повторить на новом primary. Перехватывайте эту ошибку в приложении и повторяйте. Если она часто повторяется, выясните причину выборов: нестабильность сети, тайм-ауты heartbeat, перегрузка или отставание oplog. Отслеживайте состояние набора реплик с помощью rs.status().","tags":["replica set","election","failover","interrupted","retry","state change"]},{"code":"136 CappedPositionLost","title":"Capped collection position lost","severity":"caution","meaning":"Курсор, читающий capped-коллекцию (фиксированного размера) — обычно tailable-курсор, — потерял свою позицию. Документ, на котором он был позиционирован, больше не существует, так как коллекция циклически перезаписала его.","cause":"Когда capped-коллекция заполнена, старейшие документы автоматически удаляются и перезаписываются; tailable-курсор, следящий за коллекцией, в которую пишут быстрее, чем он успевает читать, отстаёт, и его позиция перезаписывается. Встречается у потребителей, отслеживающих capped-структуры, такие как oplog.","fix":"Перепозиционируйте курсор (откройте новый tailable-курсор) и снова начните отслеживание; перехватывайте эту ошибку в приложении и обновляйте чтение. Если первопричина — потребитель, не успевающий за скоростью записи: увеличьте размер capped-коллекции/oplog или ускорьте потребителя. Для надёжного отслеживания изменений по возможности используйте change streams вместо tailable-курсоров.","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":"Обновление попыталось пройти по пути поля с точками (например, a.b.c), но часть пути наткнулась на тип, который не может содержать подполя (скалярное значение или элемент неверного типа). Поэтому пройти по пути не удалось.","cause":"Запись во вложенный путь, такой как a.b.c, когда поле 'a' в документе является скаляром (например, числом/строкой), а не объектом/массивом; либо несоответствие типов, например объект там, где ожидается индекс массива. Часто встречается в документах с несогласованной схемой (при обновлении глубоких путей через $set).","fix":"Перед обновлением убедитесь, что путь имеет ожидаемый тип (объект/массив). Исправьте несовместимые документы или сделайте обновление зависимым от структуры документа. При необходимости сначала преобразуйте поле в правильный тип (например, замените скалярное значение объектом), затем запишите вложенное поле. Для согласованности схемы рассмотрите проверку схемы (schema validation).","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":"Вы попытались создать группу потребителей в потоке с помощью XGROUP CREATE, но группа с таким же именем в этом потоке уже существует. Redis не перезаписывает существующую группу.","cause":"Приложение безусловно пытается создать группу при запуске, а группа осталась от предыдущего запуска; либо несколько экземпляров параллельно пытаются создать одну и ту же группу. Типично для неидемпотентной логики запуска.","fix":"Сделайте создание группы идемпотентным: перехватывайте и игнорируйте ошибку (существующая группа — это нормально). Используйте XGROUP CREATE stream group $ MKSTREAM, чтобы поток создавался автоматически, если его ещё нет. Выполните XINFO GROUPS stream, чтобы увидеть существующие группы. Если вы действительно хотите сбросить группу, сначала удалите её с помощью 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":"Вы выполнили команду потребления потока (XREADGROUP, XACK, XCLAIM и т. д.), но указанный ключ (поток) или группа потребителей не существует. Группа/поток, над которыми нужно работать, не найдены.","cause":"Группа так и не была создана (пропущен XGROUP CREATE); ключ потока удалён или в него никогда не писали; имя группы/ключа написано с ошибкой; либо группа удалена с помощью XGROUP DESTROY. Часто встречается, когда потребители запускаются до создания группы.","fix":"Сначала убедитесь, что поток и группа существуют: XINFO STREAM stream и XINFO GROUPS stream. Если группы нет, создайте её: XGROUP CREATE stream group $ MKSTREAM (при необходимости также создаёт поток). Добавьте при запуске приложения шаг, создающий группу (с игнорированием BUSYGROUP). Проверьте правильность написания имён ключа и группы.","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":"Вы отправили SCRIPT KILL или FUNCTION KILL, но в данный момент ни один Lua-скрипт/функция не выполняется (и не может быть завершён). Команда отклонена, так как завершать нечего.","cause":"Скрипт уже завершился к моменту попытки его остановить; SCRIPT KILL отправлен не на тот узел (а скрипт выполняется на другом экземпляре); либо состояние BUSY разрешилось само до SCRIPT KILL. Обычно это проблема синхронизации по времени.","fix":"Если ни один скрипт фактически не завис, делать ничего не нужно; это безопасный/информационный ответ. Если вы получаете ошибку BUSY (скрипт превысил busy-reply-threshold, до Redis 7 — lua-time-limit), попробуйте SCRIPT KILL (FUNCTION KILL для функций) на том узле, где выполняется скрипт. Если скрипт изменил данные, SCRIPT KILL не сработает; в крайнем случае может потребоваться SHUTDOWN NOSAVE. Разбивайте длинные скрипты на части и соблюдайте лимит времени.","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 работает в защищённом режиме (protected mode) и отклонил подключение без пароля извне (с адреса, отличного от loopback). В целях безопасности удалённый доступ без проверки подлинности заблокирован.","cause":"Redis, привязанный ко всем интерфейсам (или с нестрогой настройкой bind), получил подключение от внешнего клиента без настроенного пароля (requirepass). protected-mode включён по умолчанию и в этой ситуации блокирует запросы не с loopback. Это указывает на незащищённый Redis, случайно открытый в интернет.","fix":"Правильное решение — обеспечить защиту: задайте надёжный пароль с помощью requirepass (или пользователей ACL) и слушайте только доверенные интерфейсы с помощью bind. Если нужен удалённый доступ, защитите его паролем/TLS и ограничьте порт 6379 межсетевым экраном. protected-mode no можно временно задать только в закрытой/защищённой сети, но для установок, доступных из интернета, это НИКОГДА не рекомендуется.","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":"После перехода соединения в режим подписки Pub/Sub вы попытались выполнить обычную команду (например, GET/SET), которая в этом режиме не разрешена. Соединение в режиме подписки принимает лишь ограниченный набор команд. В старых версиях сообщение было \"ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context\".","cause":"Отправка команд работы с данными по соединению, перешедшему в режим подписки с помощью SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE. В протоколе RESP2 подписанное соединение допускает только (P|S)SUBSCRIBE, (P|S)UNSUBSCRIBE, PING, QUIT и RESET. Типичная ошибка — использование одного соединения и для pub/sub, и для обычной работы.","fix":"Используйте выделенное соединение для Pub/Sub и откройте другое для обычных команд (большинство клиентских библиотек делают это автоматически). Отправьте UNSUBSCRIBE/PUNSUBSCRIBE, чтобы выйти из режима подписки; после этого обычные команды работают. В протоколе RESP3 (HELLO 3) на подписанном соединении разрешены и другие команды; перейдите на RESP3, если ваш драйвер это поддерживает.","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":"В режиме Redis Cluster вы попытались переключиться на другую логическую базу данных (индекс DB) с помощью SELECT. Режим кластера поддерживает только базу данных 0; несколько DB использовать нельзя.","cause":"Приложение, переключающееся между DB 0-15 с помощью SELECT в автономном Redis, продолжает отправлять ту же команду после перехода на Redis Cluster. Поскольку архитектура кластера распределяет данные по слотам, концепция нескольких DB не поддерживается.","fix":"Уберите вызовы SELECT в режиме кластера; используется только DB 0. Если нужно логическое разделение, используйте префиксы ключей (пространства имён) (например, app1:key). Если определённые ключи должны обрабатываться вместе, используйте hash tag ({user123}), чтобы они попали в один слот. Сделайте конфигурацию приложения поддерживающей кластер.","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":"Команда Redis отправлена с меньшим или большим числом аргументов, чем ожидается. Команда отклонена без обработки, так как её синтаксис не соблюдён.","cause":"Отсутствующие/лишние параметры (например, просто SET key вместо SET key value); переменные, переданные пустыми/неопределёнными; вызов команды с неверным порядком аргументов; либо ошибочное раскрытие массива/аргументов (spread) в клиентской библиотеке. Часто встречается в динамически формируемых командах.","fix":"Проверьте правильную сигнатуру команды (например, SET key value [options]); сверьтесь с официальным справочником команд. В клиентском коде убедитесь, что аргументы (особенно поступающие из массивов) не пусты/не отсутствуют. Если формируете команды из пользовательского ввода, заранее проверяйте число аргументов. Попробуйте выполнить команду вручную в redis-cli, чтобы подтвердить ожидаемое число аргументов.","tags":["syntax","arguments","argument","command","syntax","cli"]},{"code":"ERR Background save already in progress","title":"Background save already in progress","severity":"caution","meaning":"Вы попытались запустить новое фоновое сохранение (BGSAVE) или перезапись AOF, но фоновое сохранение уже выполняется. Redis не допускает второго процесса сохранения одновременно.","cause":"Новый BGSAVE/BGREWRITEAOF запущен до завершения предыдущего; скрипты мониторинга или приложение многократно вызывают BGSAVE, пока fork/сохранение занимает много времени на большом наборе данных. Медленный диск или большой файл RDB затягивают процесс.","fix":"Дождитесь завершения текущего сохранения; проверьте его состояние по полям rdb_bgsave_in_progress и aof_rewrite_in_progress в INFO persistence и метке времени LASTSAVE. Сделайте логику приложения/автоматизации условной, чтобы она не запускала новый BGSAVE до завершения предыдущего сохранения. Если сохранения занимают слишком много времени, изучите скорость диска и размер RDB.","tags":["bgsave","persistence","rdb","aof","background save","fork"]},{"code":"ERR bit offset is not an integer or out of range","title":"Bit offset is not an integer or out of range","severity":"caution","meaning":"Смещение бита, переданное битовой операции, такой как SETBIT/GETBIT/BITCOUNT, недопустимо: оно не целое или находится вне допустимого диапазона. Redis не может выполнить битовую операцию по этому смещению.","cause":"Передача нечислового значения в качестве смещения; использование отрицательного смещения; либо очень большое смещение, превышающее допустимую верхнюю границу (битовая строка может быть до 512 MB, т. е. около 2^32 бит). Возникает из-за ошибок расчёта в структурах счётчиков/флагов на основе битовых карт.","fix":"Убедитесь, что смещение — неотрицательное целое число в допустимом диапазоне (от 0 до ~2^32-1; строки не более 512 MB). Проверяйте расчёты смещений (например, получаемых из идентификаторов пользователей) на переполнение/отрицательные значения. Если нужны очень большие смещения, спроектируйте данные с разбиением на несколько ключей (шардинг).","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":"Узел в данный момент не подключён к другому узлу, с которым ему нужно взаимодействовать. Запрос не удалось переслать целевому узлу, так как соединение внутри кластера (транспортный уровень) потеряно.","cause":"Сбой сети или потеря пакетов, аварийный сбой/перезапуск целевого узла, блокировка межсетевым экраном/порта (транспортный порт по умолчанию 9300), временная неотзывчивость узла из-за пауз GC либо проблема разрешения DNS/хоста. Может появляться временно при поэтапных (rolling) перезапусках.","fix":"Проверьте состояние кластера и узлов: с помощью GET _cluster/health и GET _cat/nodes?v определите, какого узла не хватает. Проверьте сетевой доступ между узлами и правила межсетевого экрана для транспортного порта (9300). Проверьте журналы затронутого узла на аварийные сбои, паузы GC или OOM. Проверьте стабильность сети и конфигурацию discovery/seed hosts.","tags":["transport","node","connection","network","9300","cluster"]},{"code":"receive_timeout_transport_exception","title":"Request timed out waiting for a response from another node","severity":"danger","meaning":"Узел отправил запрос другому узлу, но ответ не пришёл в течение заданного времени, и запрос завершился по тайм-ауту. Межузловой (транспортный) вызов не удалось завершить.","cause":"Целевой узел под высокой нагрузкой, длительные паузы GC, медленный диск/нагрузка на CPU, сетевая задержка/потеря пакетов или целевой узел слишком занят, чтобы ответить. Часто встречается при тяжёлых запросах, больших пакетных (bulk) операциях и ребалансировке шардов.","fix":"Проверьте использование ресурсов на затронутых узлах: проверьте JVM heap, GC и насыщение пулов потоков с помощью GET _nodes/stats. Проверьте отклонённые/стоящие в очереди задачи с помощью GET _cat/thread_pool?v. Измерьте сетевую задержку. Снизьте нагрузку (меньшие размеры запросов/bulk) и проверьте heap и аппаратные ресурсы. Если проблема постоянна, добавьте узлы для распределения нагрузки.","tags":["transport","timeout","gc","load","network","thread pool"]},{"code":"routing_missing_exception","title":"Routing value is required for this document","severity":"caution","meaning":"Для доступа к документу/его индексирования требуется значение routing, но оно не передано. Без routing Elasticsearch не смог определить, к какому шарду относится документ.","cause":"В маппинге индекса задано _routing required=true; используется связь parent-join (parent/child), и дочерние документы требуют routing; либо параметр routing пропущен в запросе index/get/update/delete. Типичная причина — чтение документов, записанных с пользовательским routing, без указания routing.","fix":"Явно укажите значение routing в запросе (например, ?routing=value в вызовах index/get/update/delete). При parent-join передавайте id родителя в качестве routing для дочерних документов. Для чтения/обновления/удаления используйте тот же routing, что и при записи документа. Проверьте требование _routing (required) в маппинге.","tags":["routing","shard","parent-child","join","mapping","index"]},{"code":"too_many_buckets_exception","title":"Trying to create too many buckets","severity":"caution","meaning":"Запрос агрегации попытался создать больше корзин (buckets), чем допускает верхний лимит (search.max_buckets, по умолчанию 65,536). Запрос отклонён для защиты памяти.","cause":"Агрегации, такие как terms/date_histogram, выполняются по полям с высокой кардинальностью с очень большим size или очень мелким интервалом; вложенные агрегации умножают число корзин. Лимит применяется, так как чрезмерное создание корзин может исчерпать память узла.","fix":"Уменьшите число создаваемых корзин: снизьте size агрегаций terms, увеличьте интервал date_histogram, уберите ненужные вложенные агрегации. Если нужно много результатов, получайте их постранично с помощью агрегации composite. Повышение лимита должно быть крайней мерой и несёт риск для памяти: search.max_buckets можно изменить через PUT _cluster/settings, но делайте это осторожно.","tags":["aggregation","bucket","max_buckets","terms","composite","memory"]},{"code":"shard_lock_obtain_failed_exception","title":"Obtaining shard lock timed out","severity":"danger","meaning":"Elasticsearch не смог в пределах тайм-аута получить локальную блокировку каталога (shard lock), необходимую для работы с шардом. Шард не удалось запустить или переназначить.","cause":"Предыдущая копия того же шарда (после закрытия/перемещения) ещё не освободила блокировку; медленный/зависший ввод-вывод; проблемы с диском; либо устаревшее состояние шарда, не очищенное после быстрого перезапуска узла. Обычно это временно, но если проблема сохраняется, шард остаётся UNASSIGNED.","fix":"Проверьте, почему шард не назначен: GET _cluster/allocation/explain. В большинстве случаев блокировка вскоре освобождается, и кластер сам повторяет попытку; при необходимости инициируйте повторное размещение с помощью POST _cluster/reroute?retry_failed=true. Если проблема сохраняется, изучите журналы затронутого узла на ошибки ввода-вывода/диска и проверьте состояние диска. Чистый перезапуск проблемного узла может снять блокировку.","tags":["shard","lock","unassigned","allocation","io","reroute"]},{"code":"parsing_exception","title":"Failed to parse the search request / query DSL","severity":"caution","meaning":"Отправленное тело запроса (query DSL) не удалось разобрать. Запрос не мог выполниться, так как структура JSON запроса или синтаксис запроса не соответствуют формату, ожидаемому Elasticsearch.","cause":"Некорректный JSON, неуместные/неизвестные ключи запроса, тип запроса (например, bool, range), использованный с неверной структурой, имя поля, удалённое/изменённое между версиями, либо лишняя/недостающая скобка. Часто встречается в запросах, написанных вручную или сформированных динамически.","fix":"Проверьте тело запроса, используя подсказку строки/позиции в сообщении об ошибке; убедитесь, что JSON корректен. Сравните структуру query DSL с официальным справочником (размещение bool/must/filter и т. д.). Обновите синтаксис, удалённый при обновлениях версий. Упрощайте запрос пошагово, чтобы выделить ошибочную часть; по возможности используйте построитель запросов клиентской библиотеки.","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":"В индексируемом документе терм (особенно значение поля keyword/неанализируемого поля) превысил максимально допустимую длину в байтах (32766 байт). Lucene не может проиндексировать такой длинный терм.","cause":"Очень длинное текстовое значение, записанное в поле keyword (целиком); например, всё тело документа, данные base64 или длинный URL/идентификатор в поле keyword. Поскольку поля keyword не анализируются, значение индексируется как один терм и превышает лимит.","fix":"Для длинного текста используйте поле text (анализируемое); keyword предназначен только для коротких значений, требующих точного совпадения. Чтобы ограничить значение, используйте ignore_above в маппинге (значения длиннее не индексируются, но по-прежнему хранятся). Если не нужно, задайте для очень длинных полей index:false. Пересмотрите проект полей (text или keyword) с учётом типа данных.","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":"Общее число полей в маппинге индекса превысило допустимый верхний лимит (index.mapping.total_fields.limit, по умолчанию 1000). Индексирование завершается ошибкой, так как новые поля невозможно добавить.","cause":"Обычно это взрыв маппинга (mapping explosion): при включённом динамическом маппинге неструктурированные JSON-документы с переменными ключами (например, объекты, ключи которых берутся из пользовательских данных) добавляют каждое новое поле в маппинг. Со временем число полей быстро растёт и достигает лимита.","fix":"Чтобы устранить первопричину, моделируйте данные с переменными ключами полем flattened или как массив пар ключ-значение вместо объектов; ограничьте динамический маппинг (dynamic: false или strict). Если вам действительно нужно больше полей, осторожно повысьте лимит: PUT idx/_settings {\"index.mapping.total_fields.limit\": 2000}. Отслеживайте взрыв маппинга, проверяя число полей с помощью 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":"Запрос ES|QL (_query), SQL (_sql) или EQL синтаксически разобран, но не прошёл семантическую проверку: указанный индекс, поле (столбец) или функция не найдены, либо типы несовместимы. Текст ошибки указывает проблемную позицию строки/столбца (например, \"line 1:18: Unknown column [field]\"). Ошибка проверки репозитория снимков — это не этот код, а repository_verification_exception.","cause":"Опечатка в имени поля или поле, отсутствующее в маппинге; несуществующий шаблон индекса или шаблон, которому не соответствует ни один индекс; неподдерживаемая операция над полем text (например, группировка по text вместо его подполя keyword); сравнение несовместимых типов или неизвестная функция.","fix":"Исправьте запрос, используя информацию line:column из сообщения об ошибке. Проверьте имена и типы полей: GET idx/_mapping (для SQL — DESCRIBE idx / SHOW COLUMNS). Проверьте шаблон индекса с помощью GET _cat/indices. Для группировки/сортировки используйте подполе keyword (field.keyword); при несоответствии типов применяйте явное преобразование (ES|QL: TO_STRING, TO_INTEGER и т. д.).","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 попытался освободить место в Fast Recovery Area (FRA) для нового файла (архивного журнала, бэкапа, журнала flashback), но не смог высвободить необходимое пространство, так как достигнут логический лимит, заданный DB_RECOVERY_FILE_DEST_SIZE.","cause":"Размер FRA (DB_RECOVERY_FILE_DEST_SIZE) — это верхняя граница, не зависящая от физического диска, и её заполнили архивные журналы, бэкапы RMAN или журналы flashback. Первопричина обычно в том, что архивные журналы регулярно не копируются в бэкап и не удаляются, либо не удаляются устаревшие бэкапы.","fix":"Проверьте использование FRA: SELECT * FROM v$recovery_area_usage; и SELECT * FROM v$recovery_file_dest; Сделайте бэкап архивных журналов в место вне FRA и удалите их (RMAN: BACKUP ARCHIVELOG ALL FORMAT '/backup/arc_%U' DELETE INPUT;) или удалите больше не нужные бэкапы (RMAN: DELETE OBSOLETE;). При использовании Data Guard не удаляйте архивные журналы, ещё не применённые на standby. Для постоянного решения увеличьте DB_RECOVERY_FILE_DEST_SIZE, если есть место на диске. Если Flashback Database не нужна, рассмотрите её отключение.","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":"Очередь (enqueue) критичного ресурса (например, CF — enqueue управляющего файла) удерживалась процессом намного дольше допустимого, и Oracle завершил этот процесс, чтобы предотвратить зависание системы. Появляется в alert.log вместе с записью 'killing enqueue blocker'.","cause":"Фоновый или пользовательский процесс удерживал важную очередь дольше допустимого времени из-за узкого места ввода-вывода, высокой нагрузки на CPU или зависания ОС (swapping, медленные диски); для enqueue управляющего файла (CF) это время задаётся _controlfile_enqueue_timeout (по умолчанию 900 с). Скрытые параметры (с подчёркиванием) следует менять только под руководством Oracle Support. Обычно вызывается задержками хранилища или медленными процессами, такими как LGWR/CKPT.","fix":"Изучите alert.log и трассировочный файл завершённого процесса, чтобы определить, какая очередь (обычно управляющего файла) и какой фоновый процесс ответственны. Реальная причина обычно в задержках хранилища/ввода-вывода: проверьте время отклика дисков, multipath и состояние контроллеров. Снизьте нагрузку на CPU/память и использование swap. При повторении передайте анализ трассировки в 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":"Истёк тайм-аут ожидания получения library cache lock/pin на объект (пакет, процедуру, тип и т. д.). Обычно возникает при попытке скомпилировать или изменить объект PL/SQL, пока он выполняется или заблокирован другим сеансом.","cause":"Изменяемый/компилируемый объект активно выполняется в другом сеансе или заблокирован другим DDL, поэтому исключительную блокировку library cache получить невозможно. Блокировку может удерживать долгий вызов процедуры или зависший сеанс.","fix":"Найдите сеанс, блокирующий объект: SELECT * FROM dba_kgllock WHERE kgllkhdl IN (SELECT p1raw FROM v$session WHERE event LIKE 'library cache%'); или определите сеанс, использующий объект, с помощью v$access и v$session. Повторите DDL/компиляцию в окно обслуживания с низкой нагрузкой. При необходимости безопасно завершите сеанс. Кратковременная остановка приложения облегчает получение блокировки.","tags":["library cache","lock","plsql","ddl","compile","timeout"]},{"code":"ORA-04061","title":"existing state of package/procedure has been invalidated","severity":"caution","meaning":"Существующее состояние вызванного объекта PL/SQL (пакета, процедуры, функции) стало недействительным, так как изменился объект, от которого он зависит. Это вариант потери состояния, сопровождаемый ORA-04068; состояние пакета, хранимое сеансом, больше недействительно.","cause":"Пока сеанс использовал пакет, к этому пакету или его зависимостям был применён DDL (изменение структуры таблицы, перекомпиляция, изменение привилегий). В результате существующее состояние пакета в сеансе стало недействительным. Встречается в средах с частыми развёртываниями, когда изменения схемы вносятся при работающем приложении.","fix":"Тот же вызов обычно успешен со второй попытки (так как состояние сброшено); перехватывайте эту ошибку в приложении и безопасно повторяйте. Вносите изменения схемы при низком трафике приложения/в окно обслуживания. Массово компилируйте недействительные объекты: EXEC DBMS_UTILITY.COMPILE_SCHEMA('APP_SCHEMA'); Проекты, минимизирующие состояние пакетов (глобальные переменные), уменьшают этот эффект.","tags":["plsql","package","invalidate","ddl","state","recompile"]},{"code":"ORA-01466","title":"unable to read data - table definition has changed","severity":"caution","meaning":"Запрос, читающий данные на прошлый момент времени (Flashback Query, AS OF или согласованное чтение), завершился ошибкой, так как определение таблицы (DDL) изменилось между запрошенным моментом в прошлом и текущим. Oracle не может безопасно прочитать данные на тот момент с текущим определением.","cause":"После метки времени, на которую нацелен запрос Flashback/AS OF, к таблице был применён DDL (добавление/удаление столбца, изменение типа). Кроме того, расхождение часов клиента и сервера или запросы к совсем недавнему прошлому (несколько секунд) могут вызвать эту ошибку из-за разрешения SCN/времени.","fix":"Выполняйте flashback-запрос на момент/SCN до изменения DDL таблицы. По возможности используйте точный SCN вместо метки времени (AS OF SCN). Для запросов к совсем недавнему прошлому отступите на несколько секунд назад. Если нужен постоянный доступ к истории, рассмотрите версионирование или проект с архивной таблицей вместо DDL.","tags":["flashback","as of","ddl","consistent read","scn","query"]},{"code":"ORA-08176","title":"consistent read failure; rollback data not available","severity":"danger","meaning":"При построении образа согласованного чтения запрос наткнулся на блоки, изменённые операцией, не генерирующей undo; поскольку для этих блоков нет данных undo/rollback, согласованный образ построить не удалось. В отличие от ORA-01555, undo не был перезаписан, а вообще не генерировался. (Ошибка изоляции serializable — отдельный код: ORA-08177.)","cause":"Во время выполнения запроса над тем же объектом выполняется операция, не генерирующая undo: прямые загрузки (direct-path: INSERT /*+ APPEND */, SQL*Loader DIRECT=TRUE), CREATE INDEX / перестроение индекса или дискретные транзакции.","fix":"Повторно выполните запрос после завершения direct-path загрузки или операции с индексом (повтор). Не планируйте длинные отчёты и задания direct-path загрузки / обслуживания индексов над одним объектом в одно и то же время. При повторении определите конфликтующее задание по alert log и ASH и разнесите расписания.","tags":["undo","consistent read","direct path","append","create index","retry"]},{"code":"ORA-00020","title":"maximum number of processes (N) exceeded","severity":"danger","meaning":"Число процессов экземпляра достигло верхнего лимита, заданного параметром инициализации PROCESSES, и новое соединение/процесс создать невозможно. Новые сеансы не могут подключиться; даже администратор может не суметь подключиться.","cause":"К превышению лимита приводят слишком большой пул соединений приложения, никогда не закрываемые соединения (утечки), больше одновременных пользователей, чем ожидалось, или слишком низкое значение параметра PROCESSES. Иногда процессы также занимают зависшие фоновые задания или процессы параллельных запросов.","fix":"Можно подключиться через sqlplus -prelim / as sysdba, но в этом соединении нельзя выполнять SQL (v$session не запросить); оно пригодно только для oradebug (например, hanganalyze) и SHUTDOWN ABORT. Чтобы освободить слоты, осторожно завершите простаивающие серверные процессы приложения на уровне ОС (ps -ef | grep 'LOCAL=NO'); затем в обычном соединении SYSDBA найдите утёкшие сеансы через v$session/v$process и закройте их с помощью ALTER SYSTEM KILL SESSION. Для постоянного решения увеличьте PROCESSES и перезапустите экземпляр (если SESSIONS явно задан в spfile, он не пересчитывается; обновите и его). На стороне приложения исправьте максимум пула соединений и очистку простаивающих соединений.","tags":["processes","process","connection","limit","pool","init parameter"]},{"code":"ORA-01113","title":"file N needs media recovery","severity":"danger","meaning":"Файл данных переводится в online или база данных открывается, но файл несогласован; прежде чем продолжить, необходимо применить восстановление носителя. Файл находится на более старом/несогласованном SCN, чем ожидает управляющий файл.","cause":"Файл данных восстановлен из старого бэкапа, redo не применялся, пока он был в offline, либо файл не актуален после аварийного сбоя экземпляра/хранилища. Обычно вызвано пропуском шага recover после restore или долгим нахождением файла в offline.","fix":"Восстановите файл или базу данных: RECOVER DATAFILE N; или RECOVER DATABASE; затем ALTER DATABASE DATAFILE N ONLINE; или ALTER DATABASE OPEN; Убедитесь, что необходимые архивные журналы доступны. С RMAN: при необходимости после RESTORE выполните RECOVER DATAFILE N;. Следите по alert.log, какие журналы запрашиваются.","tags":["media recovery","datafile","redo","restore","recover","scn"]},{"code":"ORA-02393","title":"exceeded call limit on CPU usage","severity":"caution","meaning":"Превышено время CPU, допустимое для одного вызова (CPU_PER_CALL), заданное в профиле ресурсов, назначенном сеансу, и Oracle завершил этот вызов. Это защитный механизм ограничения ресурсов.","cause":"В профиле пользователя задан лимит CPU_PER_CALL, и RESOURCE_LIMIT=TRUE; один вызов SQL/PLSQL превысил этот порог CPU. Обычно вызвано неэффективным/неконтролируемым запросом, потребляющим гораздо больше CPU, чем ожидалось.","fix":"Оптимизируйте запрос (подходящий индекс, правильный план, устранение ненужных полных сканирований таблиц), чтобы потребление CPU опустилось ниже лимита. Если бизнес-требование обосновано, увеличьте CPU_PER_CALL в профиле соответствующего пользователя или задайте UNLIMITED: ALTER PROFILE ... LIMIT CPU_PER_CALL ...; Рассмотрите более гибкое управление ресурсами с помощью Database Resource Manager вместо профилей.","tags":["profile","cpu_per_call","resource_limit","resource","query","optimization"]},{"code":"ORA-12801","title":"error signaled in parallel query server Pnnn","severity":"danger","meaning":"В выполняющемся параллельно запросе один из серверов параллельного выполнения (parallel query slaves) столкнулся с ошибкой, и эта ошибка была передана координатору запроса. ORA-12801 — это обёртка; фактическая ошибка находится во второй строке после неё.","cause":"Подчинённый процесс столкнулся с реальной ошибкой во время параллельной операции: недостаточно табличного пространства TEMP (ORA-01652), исчерпание памяти PGA (ORA-04030), повреждение данных, лимиты параллельных процессов или другая исходная ошибка SQL. ORA-12801 лишь сообщает, что она произошла в параллельном контексте.","fix":"Сосредоточьтесь на фактическом коде ошибки, следующем сразу за ORA-12801, и устраните её (например, увеличение TEMP, настройка PGA, исправление повреждённого блока). При необходимости выделите фактическую ошибку, выполнив запрос последовательно, убрав подсказки PARALLEL или снизив степень параллелизма. Проверьте PARALLEL_MAX_SERVERS и использование ресурсов.","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":"Нарушено ограничение EXCLUDE (исключения). Этот тип ограничения не допускает конфликта двух строк по определённым операторам (например, перекрытие диапазонов &&); вставленная/обновлённая строка отклонена, так как конфликтует с существующей строкой.","cause":"Обычно определено ограничение EXCLUDE на основе GiST (например, запрещающее перекрытие времени через tstzrange && для бронирования встреч/помещений), и новая строка имеет диапазон/значение, перекрывающееся с существующей строкой. Также может возникать, когда две параллельные транзакции вставляют перекрывающиеся диапазоны.","fix":"Проверяйте перекрытие запросом перед вставкой или перехватывайте ошибку в приложении и показывайте пользователю понятное сообщение. Запросите соответствующий диапазон/условие, чтобы найти конфликтующую существующую строку. Если бизнес-правило допускает перекрытия, ослабьте ограничение; иначе исправьте входные данные. При параллельной работе безопасно повторяйте транзакцию для её сериализации.","tags":["exclude","constraint violation","gist","range","overlap","constraint"]},{"code":"23001","title":"restrict_violation","severity":"danger","meaning":"Код restrict_violation стандарта SQL. Важно: PostgreSQL сообщает о нарушении внешнего ключа, определённого с ON DELETE/ON UPDATE RESTRICT или NO ACTION, не этим кодом, а кодом 23503 (foreign_key_violation); на практике вы не увидите этот код от ядра PostgreSQL.","cause":"При удалении родительской строки, имеющей зависимые дочерние строки, или изменении её ключа PostgreSQL возвращает 23503. 23001 обычно встречается в коде приложения, перенесённом с другой базы данных, или как пользовательская ошибка расширения. В отличие от NO ACTION, RESTRICT не может отложить проверку до конца транзакции и завершается ошибкой немедленно.","fix":"Сначала удалите/обновите зависимые дочерние строки или перенесите их к другому родителю; затем обработайте родительскую строку. Если бизнес-правило позволяет, рассмотрите переопределение внешнего ключа с ON DELETE CASCADE или SET NULL. При массовых удалениях соблюдайте правильный порядок удаления (сначала дочерние, затем родительские).","tags":["foreign key","restrict","fk","delete","reference","constraint"]},{"code":"42P17","title":"invalid_object_definition","severity":"danger","meaning":"Определение объекта (функции, правила сортировки (collation), триггера, правила и т. д.) недопустимо. PostgreSQL указывает, что заданное определение внутренне противоречиво или неприемлемо; объект невозможно создать/изменить в таком виде.","cause":"Частые причины: противоречивые сочетания атрибутов функции (например, помечена IMMUTABLE, но ведёт себя как volatile), недопустимое определение collation/набора символов или неподходящее определение триггера/правила. Причиной также могут быть несогласованные параметры защиты на уровне строк (RLS) и некоторых DDL.","fix":"Прочитайте подробности ошибки (DETAIL/HINT), чтобы определить, какая часть определения отклонена. Приведите метки изменчивости функций в соответствие с фактическим поведением (VOLATILE/STABLE/IMMUTABLE). Убедитесь, что имя collation существует в pg_collation. Исправьте определение согласно документированному синтаксису/правилам и выполните его снова.","tags":["function","collation","definition","volatility","ddl","object"]},{"code":"42710","title":"duplicate_object","severity":"caution","meaning":"Создаваемый объект (роль, тип, ограничение, расширение, схема, имя индекса и т. д.) уже существует. PostgreSQL не допускает второго объекта с тем же именем/определением.","cause":"Команда CREATE ROLE, CREATE TYPE, CREATE EXTENSION или ADD CONSTRAINT и т. п. выполнена, когда объект с тем же именем уже существовал. Обычно вызвано двойным запуском скрипта, неидемпотентными миграциями или попыткой двух параллельных сеансов создать один и тот же объект.","fix":"Проверьте, существует ли объект (pg_roles для ролей, pg_type для типов, pg_extension для расширений). Пишите миграции идемпотентно: используйте CREATE ... IF NOT EXISTS, где доступно, или сначала выполняйте DROP ... IF EXISTS. При конфликте ограничений переименуйте существующее ограничение или удалите и создайте его заново.","tags":["duplicate","role","type","extension","constraint","migration"]},{"code":"22008","title":"datetime_field_overflow","severity":"caution","meaning":"При вычислении или разборе значения даты/времени одно из полей вышло за допустимый диапазон. Например, получен невозможный компонент, такой как месяц 13 или день 32, либо результат за пределами поддерживаемого диапазона времени.","cause":"Недопустимая входная дата (например, '2026-02-30'), компонент вне диапазона или сложение с INTERVAL, выходящее за поддерживаемый диапазон дат. Типичная причина — разбор с неверным форматом/локалью или вставка непроверенного пользовательского ввода.","fix":"Проверяйте входные даты в приложении перед вставкой и отправляйте их в правильном формате (предпочтительно ISO 8601: YYYY-MM-DD). При разборе с помощью TO_DATE/TO_TIMESTAMP приводите маску формата в соответствие с входными данными. Держите результаты арифметики в пределах поддерживаемого диапазона дат; пропускайте подозрительные значения через проверки границ.","tags":["date","time","overflow","interval","validation","format"]},{"code":"22026","title":"string_data_length_mismatch","severity":"caution","meaning":"Длина битовой строки не соответствует фиксированной длине, требуемой операцией. В PostgreSQL чаще всего появляется, когда битовая строка другой длины записывается в тип фиксированной длины bit(n) или когда побитовые AND/OR/XOR применяются к битовым строкам разной длины.","cause":"Запись битовой строки короче/длиннее n в столбец bit(n) (например, B'101' в столбец bit(4): 'bit string length 3 does not match type bit(4)') или применение &, |, # к битовым строкам разной длины ('cannot AND bit strings of different sizes'). Ошибка не связана с текстом или кодировкой символов; слишком длинное значение для char(n)/varchar(n) вызывает вместо этого 22001.","fix":"Приведите значение к целевой длине: используйте явное приведение (B'101'::bit(4) дополняет нулями справа и отсекает избыток) или определите столбец как bit varying(n), если длина может меняться. Перед побитовыми операциями приводите операнды к одной длине; проверяйте длину с помощью length().","tags":["bit","length","bit string","varbit","conversion","validation"]},{"code":"2201B","title":"invalid_regular_expression","severity":"caution","meaning":"Шаблон, переданный операции с регулярным выражением (~, ~*, SIMILAR TO, regexp_matches, substring и т. д.), недопустим. PostgreSQL не смог скомпилировать шаблон, так как его синтаксис неверен.","cause":"Шаблон regex с несбалансированными скобками, недопустимыми классами символов, неверными escape-последовательностями или неподдерживаемой конструкцией. Частая причина — использование пользовательского ввода непосредственно в качестве шаблона без экранирования специальных символов.","fix":"Исправьте шаблон regex согласно синтаксису POSIX/ARE; проверьте круглые и квадратные скобки и escape-последовательности. Если вы ищете пользовательский ввод как буквальный текст, а не как шаблон: в PostgreSQL нет встроенной функции regexp_quote, поэтому экранируйте специальные символы, добавляя перед ними обратную косую черту через regexp_replace, либо вовсе откажитесь от regex и используйте strpos()/position() или LIKE ... ESCAPE. Проверьте шаблон, протестировав его в отдельном инструменте.","tags":["regex","regular expression","pattern","similar to","escape","syntax"]},{"code":"44000","title":"with_check_option_violation","severity":"danger","meaning":"INSERT/UPDATE через представление, определённое WITH CHECK OPTION, отклонён, так как получившаяся строка больше не удовлетворяет условию WHERE представления. Представление не допускает записи строк, которые оказались бы вне его собственного фильтра.","cause":"Обновляемое представление определено WITH CHECK OPTION, и запись выводит строку за пределы определяющего условия представления (например, перевод строки в 'inactive', когда представление фильтрует status='active'). Аналогичная проверка может срабатывать в политиках защиты на уровне строк (RLS).","fix":"Убедитесь, что записываемые значения удовлетворяют условию WHERE представления, или выполните операцию непосредственно над базовой таблицей вместо представления (если позволяют привилегии). Если бизнес-правило допускает выход строки за пределы фильтра, переопределите представление без WITH CHECK OPTION. Для RLS проверьте выражение WITH CHECK политики.","tags":["view","check option","updatable view","rls","insert","update"]},{"code":"40002","title":"transaction_integrity_constraint_violation","severity":"danger","meaning":"Код стандарта SQL для нарушения при COMMIT ограничения целостности, проверяемого в конце транзакции. Важно: PostgreSQL на практике не выдаёт этот код; нарушения отложенных (DEFERRED) ограничений при COMMIT он возвращает с собственным кодом ограничения: 23503 для внешних ключей, 23505 для уникальности.","cause":"Внешний ключ или ограничение уникальности, определённое как DEFERRABLE INITIALLY DEFERRED, было временно нарушено на промежуточных шагах транзакции и не исправлено к моменту COMMIT; в PostgreSQL это сообщается как 23503/23505. Например, к этому приводит вставка взаимно ссылающихся строк в неверном порядке без последующего исправления.","fix":"Исправьте логику транзакции так, чтобы все отложенные ограничения выполнялись до COMMIT (добавьте недостающие ссылки, устраните конфликты). При необходимости проверьте ограничение заранее внутри транзакции с помощью SET CONSTRAINTS ... IMMEDIATE, чтобы раньше выявить проблемный шаг. Измените порядок вставок/обновлений для взаимных ссылок.","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":"В списке SELECT или в HAVING/ORDER BY использован столбец, который не входит в GROUP BY и не обёрнут в агрегатную функцию. Поскольку включён режим SQL ONLY_FULL_GROUP_BY, MySQL отклоняет это недетерминированное выражение.","cause":"Запрос напрямую выбирает столбец, отличный от группируемых, но у этого столбца может не быть единственного значения на группу. ONLY_FULL_GROUP_BY включён по умолчанию в MySQL 5.7+; запросы, полагающиеся на старое нестрогое поведение GROUP BY, вызывают эту ошибку.","fix":"Либо добавьте столбец в список GROUP BY, либо оберните его в агрегатную функцию (MAX(), MIN(), ANY_VALUE()). Если вы знаете, что значение действительно уникально в группе, ANY_VALUE() намеренно подавляет неоднозначность. Отключение режима (удаление ONLY_FULL_GROUP_BY из sql_mode) вместо исправления запроса возвращает риск неверных результатов; избегайте этого.","tags":["group by","only_full_group_by","sql_mode","any_value","aggregate","query"]},{"code":"1267","title":"Illegal mix of collations","severity":"caution","meaning":"В операции сравнения или объединения (JOIN, WHERE, UNION, CONCAT) были объединены две строки с разными правилами сортировки (collation), и MySQL не смог определить, какие правила применить.","cause":"Обычно это сравнение столбцов с разными наборами символов/collation (например, utf8mb4_general_ci и utf8mb4_unicode_ci или latin1 и utf8mb4) либо несоответствие между столбцом и collation соединения. Часто встречается, когда таблицы/базы данных создавались с разными значениями по умолчанию.","fix":"Приведите обе стороны сравнения к одному collation: укажите явный collation с помощью COLLATE (например, col1 = col2 COLLATE utf8mb4_unicode_ci) или используйте CONVERT(... USING ...). Для постоянного решения преобразуйте затронутые столбцы/таблицы к единому согласованному набору символов и collation. Также согласуйте collation соединения (collation_connection).","tags":["collation","collation mismatch","character set","join","utf8mb4","convert"]},{"code":"1030","title":"Got error N from storage engine","severity":"danger","meaning":"Подсистема хранения (InnoDB/MyISAM) не смогла завершить операцию и вернула низкоуровневый код ошибки. Это общее сообщение о проблеме диска, файловой системы или внутренней проблеме движка ниже уровня SQL. Подкод в скобках/сообщении показывает фактическую причину.","cause":"Частые причины: заполнен диск/временное пространство (например, errno 28 - No space left on device), проблемы с правами доступа к файлам, повреждённые файлы таблиц, проблемы табличного пространства InnoDB или ошибки ввода-вывода на уровне ОС. Причиной также могут быть запросы сортировки/группировки, создающие большие временные таблицы.","fix":"Расшифруйте подкод ошибки (в Linux perror N показывает его значение, например perror 28). Проверьте использование диска и место в tmpdir; освободите место. Проверьте владельца/права доступа к файлам. Для InnoDB изучите подробности в журнале ошибок; если подозревается повреждение, выполните CHECK TABLE и при необходимости восстановите. Оптимизируйте запросы, создающие огромные временные результаты.","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":"Данные binlog, созданные одной транзакцией, превысили лимит памяти/кеша, заданный max_binlog_cache_size. Транзакция не поместилась в двоичный журнал, поэтому завершилась ошибкой и была откачена.","cause":"Очень большая транзакция (один INSERT/UPDATE/DELETE миллионов строк или огромная массовая загрузка) заполнила кеш binlog. При включённом двоичном журналировании (репликация/восстановление на момент времени) большие транзакции легко превышают этот лимит.","fix":"Разбейте большую транзакцию на меньшие пакеты; выполняйте COMMIT после каждого пакета. Если действительно больших транзакций не избежать, увеличьте max_binlog_cache_size (и при необходимости binlog_cache_size). В ETL/массовых загрузках загрузка частями с промежуточными COMMIT предотвращает и эту ошибку, и длительно удерживаемые блокировки.","tags":["binlog","max_binlog_cache_size","transaction","batch","replication","bulk"]},{"code":"1785","title":"Statement violates GTID consistency","severity":"danger","meaning":"При включённой репликации на основе GTID (Global Transaction Identifier) выполненный оператор нарушает правила согласованности GTID. Режим GTID не допускает некоторых типов операторов, которые невозможно безопасно назначить одному GTID.","cause":"1785 (ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE) возникает именно тогда, когда нетранзакционные таблицы (например, MyISAM) обновляются в той же транзакции, что и транзакционные (InnoDB), или в многооператорной транзакции без autocommit. В старых версиях согласованность GTID также нарушают CREATE TABLE ... SELECT (до 8.0.21; в 8.0.21+ разрешено для движков с поддержкой атомарного DDL) и CREATE/DROP TEMPORARY TABLE внутри транзакции (до 8.0.13; в 8.0.13+ разрешено при binlog_format ROW/MIXED) (о них сообщается отдельными кодами ошибок). Они отклоняются при enforce_gtid_consistency=ON.","fix":"Не смешивайте таблицы нетранзакционных движков с транзакционными таблицами в одной транзакции; обновляйте таблицы MyISAM одиночными операторами в режиме autocommit или переведите все таблицы на InnoDB: ALTER TABLE my_table ENGINE=InnoDB; В старых версиях (до 8.0.21) разбейте CREATE TABLE ... SELECT на два шага (CREATE TABLE, затем INSERT ... SELECT); до 8.0.13 создавайте и удаляйте временные таблицы вне явной транзакции.","tags":["gtid","replication","consistency","temporary table","myisam","innodb"]},{"code":"3024","title":"Query execution was interrupted, maximum statement execution time exceeded","severity":"caution","meaning":"Запрос SELECT прерван MySQL, так как превысил заданное для него максимальное время выполнения (max_execution_time / подсказка MAX_EXECUTION_TIME). Это защитный тайм-аут, не позволяющий долгим запросам занимать ресурсы.","cause":"Запрос выполнялся дольше значения max_execution_time сеанса/глобального или подсказки оптимизатора MAX_EXECUTION_TIME(N) в SELECT. Обычно вызвано неэффективным планом, отсутствующим индексом, большим сканированием или замедлением при высокой параллельной нагрузке. (Применяется только к SELECT только для чтения.)","fix":"Проанализируйте запрос с помощью EXPLAIN и оптимизируйте его (подходящие индексы, более селективные предикаты, меньше полных сканирований таблиц). Если запросу обоснованно нужно выполняться долго, увеличьте подсказку MAX_EXECUTION_TIME для этого SELECT или max_execution_time сеанса. Выполняйте тяжёлые аналитические запросы в offline/на реплике.","tags":["timeout","max_execution_time","query","explain","index","select"]},{"code":"1298","title":"Unknown or incorrect time zone","severity":"caution","meaning":"Указанное имя часового пояса не распознано MySQL. Вызов CONVERT_TZ(), параметр time_zone или операция, ссылающаяся на именованный часовой пояс (например, 'Europe/Istanbul'), завершились ошибкой, так как этот часовой пояс не определён на сервере.","cause":"Чтобы MySQL мог использовать именованные часовые пояса, должны быть загружены таблицы часовых поясов в схеме mysql (time_zone, time_zone_name и т. д.). Если эти таблицы пусты, работают только числовые смещения ('+03:00'); именованные пояса неизвестны, и возникает эта ошибка.","fix":"Загрузите таблицы часовых поясов. В Linux импортируйте системные данные zoneinfo с помощью mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql; затем перезапустите сервер или выполните FLUSH TABLES. Если загрузить именованные пояса невозможно, используйте числовое смещение ('+03:00'). Проверьте написание имени (регистр, '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":"Создавалась хранимая функция, но двоичное журналирование включено, а функция не объявлена ни с одной из характеристик детерминированности/доступа к данным. MySQL отклоняет такую функцию, так как она неоднозначна с точки зрения безопасности репликации.","cause":"Функция определена без характеристик DETERMINISTIC, NO SQL или READS SQL DATA при log_bin = ON и log_bin_trust_function_creators=OFF. Без этих характеристик MySQL не может быть уверен, что функция даст тот же результат на реплике.","fix":"Объявите поведение функции правильными характеристиками: добавьте DETERMINISTIC, если она действительно детерминирована, NO SQL, если она не обращается к данным, READS SQL DATA, если только читает. Если вы уверены в безопасности функций, можно задать log_bin_trust_function_creators=1 в качестве централизованной политики; однако правильное объявление характеристики — самый безопасный подход. Убедитесь, что характеристика соответствует фактическому поведению функции.","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":"Значение, присваиваемое системной/сеансовой переменной (через SET), недопустимо. Значение отклонено, так как находится вне допустимого набора, имеет неверный тип или неподходящий формат.","cause":"Частые примеры: присваивание sql_mode недопустимого/написанного с ошибкой имени режима, указание неопределённого варианта для переменной типа enum или присваивание NULL переменной, не допускающей NULL. Причиной также могут быть числовые значения в неверных единицах/диапазоне.","fix":"Проверьте допустимые значения и тип, принимаемые переменной; для переменных типа ENUM/SET (например, sql_mode, имеющей тип SET) пишите имена значений/режимов точно и разделяйте несколько значений запятыми. Проверьте допустимый диапазон значений в документации. Для списков, таких как sql_mode, прочитайте текущее значение (SELECT @@sql_mode) и безопасно добавляйте/удаляйте элементы поверх него.","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":"Истёк тайм-аут запроса при ожидании на семафоре ресурсов (resource semaphore) выделения памяти (memory grant), необходимого для выполнения. Поскольку нехватка памяти не позволила выделить грант, операция была отменена.","cause":"Параллельные запросы, интенсивно использующие память (большие сортировки, hash join, hash-агрегаты), заняли весь пул памяти запросов; новые запросы встали в очередь на семафоре и завершились по тайм-ауту. Первопричина — завышенные оценки выделения памяти (устаревшая статистика, плохие планы) или недостаток памяти сервера.","fix":"Определите запросы, ожидающие или удерживающие память, с помощью sys.dm_exec_query_memory_grants и DMV семафора ресурсов. Обновите статистику и исправьте планы, вызывающие завышенные выделения памяти (подходящие индексы, лучшие оценки). Снизьте параллелизм запросов, интенсивно использующих память; при необходимости ограничьте выделения памяти с помощью Resource Governor. Проверьте конфигурацию памяти сервера (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":"Выделение памяти, необходимое запросу, было недоступно, и операцию выполнить не удалось. В отличие от 8645, это не ожидание, а прямой отказ в предоставлении запрошенного большого блока памяти; обычно указывает на серьёзную нехватку памяти.","cause":"Доступная память запросов на сервере не может удовлетворить грант, запрошенный одним оператором (например, очень большой сортировкой/хешированием). Причиной могут быть чрезмерно высокие оценки памяти, низкий max server memory, внешняя нагрузка на память или низкий лимит Resource Governor — REQUEST_MAX_MEMORY_GRANT_PERCENT (группа рабочей нагрузки) или MAX_MEMORY_PERCENT (пул ресурсов).","fix":"Оптимизируйте запросы, раздувающие выделения памяти (обновление статистики, правильное индексирование, разбиение запроса). Отслеживайте общую и доступную память; проверьте параметр max server memory и нагрузку на память со стороны внешних приложений. Если используется Resource Governor, проверьте параметры REQUEST_MAX_MEMORY_GRANT_PERCENT группы рабочей нагрузки и MAX_MEMORY_PERCENT пула. Перенесите пакетные задания, интенсивно использующие память, на часы низкой нагрузки.","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":"Операция ALTER DATABASE (например, переход в однопользовательский режим, изменение файла, установка параметра) не смогла получить необходимую блокировку базы данных, так как её используют другие активные соединения/сеансы.","cause":"Операция предпринята, когда база данных использовалась; исключительную блокировку, необходимую для SET SINGLE_USER, RESTRICTED_USER или аналогичных изменений параметров, не удалось получить из-за активных пользовательских соединений. Часто встречается при частых развёртываниях/обслуживании при открытых соединениях приложения.","fix":"Определите другие сеансы, подключённые к базе данных (sys.dm_exec_sessions, sp_who2), и безопасно закройте их. Для обслуживания меняйте режим с откатом открытых транзакций параметром ROLLBACK: ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; Выполняйте операцию при низком трафике приложения или в окно обслуживания. По завершении не забудьте вернуться в MULTI_USER.","tags":["alter database","lock","single_user","connection","maintenance","rollback"]},{"code":"3702","title":"Cannot drop database because it is currently in use","severity":"caution","meaning":"Предпринята попытка удалить или переименовать базу данных, но она отклонена, так как база данных всё ещё используется активными соединениями. SQL Server не может удалить используемую базу данных.","cause":"К удаляемой базе данных подключены другие сеансы (пул приложения, открытая вкладка SSMS, фоновые задания). Причиной может быть даже ваш собственный сеанс, у которого эта база данных является контекстом (USE). Частая причина — также открытые соединения инструментов мониторинга/бэкапа.","fix":"Перед удалением переключитесь на другую базу данных (USE master;). Найдите и закройте подключённые сеансы либо сначала переведите базу данных в однопользовательский режим: ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; затем выполните DROP DATABASE [db]; Остановите пулы соединений приложения и убедитесь, что инструменты мониторинга не открывают соединения.","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":"Предпринята попытка удалить пользователя/роль базы данных (участника базы данных), но удалить его не удалось, так как участник владеет одной или несколькими схемами в базе данных. Удалить его невозможно, пока принадлежащие ему схемы не будут переданы другому участнику.","cause":"Удаляемый пользователь является владельцем (OWNER) как минимум одной схемы (например, схемы, по умолчанию созданной с его именем, или назначенной через AUTHORIZATION). SQL Server блокирует удаление, чтобы не оставлять схемы без владельца.","fix":"Найдите схемы, принадлежащие пользователю: SELECT name FROM sys.schemas WHERE principal_id = USER_ID('app_user'); Передайте владение этими схемами другому участнику (например, dbo): ALTER AUTHORIZATION ON SCHEMA::[app_schema] TO dbo; Если схема больше не нужна (и пуста), выполните DROP SCHEMA. После передачи владения снова выполните DROP USER.","tags":["schema","principal","user","owner","alter authorization","drop user"]},{"code":"1934","title":"INSERT/UPDATE failed because the following SET options have incorrect settings","severity":"danger","meaning":"Операция DML (INSERT/UPDATE/DELETE) завершилась ошибкой, так как обязательные параметры SET заданы неверно. Обычно возникает, когда не соблюдены обязательные настройки SET для индексированных представлений, индексов на вычисляемых столбцах, фильтруемых индексов или пространственных/XML-индексов.","cause":"У затронутой таблицы/представления есть объект, требующий определённых параметров SET (в частности, QUOTED_IDENTIFIER и ANSI_NULLS ON, NUMERIC_ROUNDABORT OFF и т. д.), но текущие параметры SET сеанса этому требованию не соответствуют. Причиной бывают старые клиентские драйверы или скрипты, явно задающие неверные параметры SET.","fix":"Задайте необходимые параметры для соединения/сеанса: 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; Убедитесь, что драйвер приложения правильно передаёт эти параметры при подключении. В сообщении об ошибке перечислены неверные параметры; исправьте их.","tags":["set options","indexed view","computed column","quoted_identifier","ansi_nulls","dml"]},{"code":"468","title":"Cannot resolve the collation conflict","severity":"caution","meaning":"В операции (JOIN, UNION, сравнение в WHERE или проверка равенства) встретились два строковых выражения с разными правилами сортировки (collation), и SQL Server не смог определить, какой collation применить. Операция отменена из-за неоднозначности.","cause":"Сравниваемые столбцы определены с разными collation; чаще всего встречается, когда collation базы данных отличается от collation сервера/tempdb или когда соединяются таблицы из баз данных с разными collation. Классический триггер — соединение временных таблиц (collation tempdb) с постоянными таблицами.","fix":"Укажите явный collation в сравнении: ... WHERE a.col = b.col COLLATE DATABASE_DEFAULT (или конкретное имя collation). Используйте COLLATE DATABASE_DEFAULT при создании столбцов временных таблиц. Для постоянного решения приведите затронутые столбцы/базы данных к единому согласованному collation. Если несоответствия с tempdb часты, DATABASE_DEFAULT — наиболее практичное решение.","tags":["collation","collation","join","tempdb","database_default","conflict"]},{"code":"4864","title":"Bulk load data conversion error (type mismatch or invalid character)","severity":"caution","meaning":"При массовой загрузке (BULK INSERT, bcp или OPENROWSET(BULK...)) поле в исходном файле не удалось преобразовать в тип данных целевого столбца. Данные в указанной строке/столбце не соответствуют ожидаемому типу.","cause":"Значение в исходном файле данных не соответствует типу целевого столбца: текст в числовом столбце, неверный формат даты, неожиданный десятичный разделитель/разделитель тысяч, неверная кодировка символов (UTF-8/UTF-16) или смещение столбцов из-за неверных разделителей полей/строк. Частая причина — также непропущенная строка заголовка.","fix":"Перейдите к строке и столбцу, указанным в ошибке, и изучите исходные данные. Приведите разделители полей (FIELDTERMINATOR) и строк (ROWTERMINATOR) в соответствие с файлом; используйте FIRSTROW=2, чтобы пропустить строку заголовка. Задайте CODEPAGE или DATAFILETYPE для правильной кодовой страницы/кодировки. Сделайте форматы дат/чисел совместимыми с целевым типом. Файл форматирования (format file) делает сопоставление надёжным.","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":"Имя входа попыталось обратиться к определённой базе данных, но не имеет к ней доступа в текущем контексте безопасности. Имя входа может подключиться к серверу, но не имеет соответствующего пользователя/разрешения в целевой базе данных.","cause":"У имени входа нет сопоставленного пользователя в базе данных (потерянный или так и не созданный), доступ к базе данных ограничен или учётная запись guest отключена. Частый случай — обозреватель объектов SSMS пытается прочитать метаданные базы данных, к которой у имени входа нет доступа.","fix":"Создайте пользователя для имени входа в целевой базе данных и выдайте необходимые разрешения: USE [db]; CREATE USER [login] FOR LOGIN [login]; и назначьте подходящие роли/разрешения. Если существует потерянный пользователь, сопоставьте его с помощью ALTER USER ... WITH LOGIN = ... Если доступ действительно нужен, добавьте его в подходящую роль базы данных; если нет, ошибка — ожидаемое поведение (нет доступа к этой базе данных).","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":"В наборе реплик идут выборы primary. Пока выборы не завершены, primary не определён, поэтому запись (и некоторые чтения) временно не принимается.","cause":"Кластер выбирает новый primary после отказа текущего primary, разделения сети, stepdown для обслуживания или перезапуска узла. Выборы обычно занимают несколько секунд; операции, поступающие в это окно, сталкиваются с этим состоянием.","fix":"Это временное состояние; включите функцию повтора в драйвере (retryable writes/reads), чтобы операция автоматически повторялась по завершении выборов. Если выборы частые или долгие, выясните первопричину: стабильность сети, состояние узлов, конфигурацию голосов и настройки тайм-аута выборов (settings.electionTimeoutMillis). Проверьте состояние кластера с помощью rs.status().","tags":["replica set","election","primary","retry","step-down","high availability"]},{"code":"225 TransactionTooOld","title":"TransactionTooOld","severity":"caution","meaning":"Команда выполнена внутри многодокументной транзакции, но её номер транзакции старше последнего номера транзакции, который сервер видел для этого сеанса. Иначе говоря, начата более новая транзакция, поэтому эта старая больше недействительна.","cause":"В том же сеансе начата новая транзакция до завершения старой, либо номера транзакций сдвинулись не по порядку из-за параллелизма. Обычно происходит, когда объект сеанса неправильно используется совместно или несколько потоков используют один сеанс.","fix":"Правильно управляйте жизненным циклом сеанса для каждой логической транзакции; не используйте один сеанс параллельно в нескольких потоках. Выполняйте транзакции через рекомендуемый драйвером API withTransaction/callback (он обрабатывает повторы и управление номерами). Завершайте предыдущую транзакцию через commit/abort перед началом новой.","tags":["transaction","session","session misuse","concurrency","withtransaction","multi-document transaction"]},{"code":"246 SnapshotUnavailable","title":"SnapshotUnavailable","severity":"caution","meaning":"Чтение с запрошенным read concern snapshot (readConcern: snapshot) выполнить не удалось, так как сервер не может предоставить согласованный снимок на этот момент времени. Обычно связано с меткой времени чтения, которая ещё недостаточно продвинулась или отстала.","cause":"Используется readConcern 'snapshot' (в основном в многодокументных транзакциях), но согласованное представление для запрошенной метки времени atLeast/at ещё недоступно; например, из-за недавно созданной коллекции/индекса, изменения каталога или отставания репликации. В этом случае сервер отклоняет чтение.","fix":"Обычно это временно; повторите операцию после короткой паузы (механизм повтора драйвера для временных ошибок транзакций это покрывает). Если вы недавно выполняли DDL (создание коллекции/индекса), начинайте транзакцию после него. Проверьте отставание репликации и состояние узлов. Если snapshot-чтение на самом деле не нужно, выберите более подходящий readConcern.","tags":["snapshot","readconcern","transaction","timestamp","retry","replication"]},{"code":"257 TransactionTooLarge","title":"TransactionTooLarge","severity":"danger","meaning":"Многодокументная транзакция превысила допустимые лимиты размера/числа операций. Транзакция отклонена, так как накопленные в ней изменения (запись oplog или использование памяти) превысили верхний лимит.","cause":"Изменение/вставка большого числа документов в одной транзакции слишком увеличили общий объём данных, порождаемых транзакцией (например, объём изменений, который нужно держать в кеше WiredTiger, или серверные лимиты, такие как transactionLimitBytes). Начиная с 4.2 транзакция может разбиваться на несколько записей oplog, поэтому лимит записи oplog в 16 MB сам по себе не решающий. Использование транзакций для массовой миграции данных/ETL легко упирается в этот лимит.","fix":"Разбейте транзакцию на меньшие части; фиксируйте каждый пакет в отдельной транзакции. Многодокументные транзакции рассчитаны на краткость и малый размер; крупные массовые обновления предпочтительно выполнять без транзакций (идемпотентно, по частям) или разбивать с помощью bulk write. Избегайте изменения тысяч документов в одной транзакции.","tags":["transaction","size","oplog","batch","bulk","bulk write"]},{"code":"276 IndexBuildAborted","title":"IndexBuildAborted","severity":"danger","meaning":"Выполнявшееся построение индекса прервано/остановлено до завершения. Целевой индекс не был успешно создан.","cause":"Построение индекса могло быть прервано из-за нехватки диска/памяти на узле, явной отмены через dropIndexes, stepdown primary/изменения состояния реплики, нарушения уникальности (конфликтующие документы при построении уникального индекса) или перезапуска узла.","fix":"Найдите причину прерывания в журналах mongod (место на диске, память, конфликт уникальности и т. д.) и устраните её. Если строите уникальный индекс, сначала очистите конфликтующие документы. Обеспечьте достаточно диска/памяти и убедитесь, что состояние реплик стабильно. После устранения проблемы перестройте индекс с помощью createIndex; в больших коллекциях выбирайте период низкой нагрузки.","tags":["index","index build","aborted","unique","disk","replica set"]},{"code":"86 IndexKeySpecsConflict","title":"IndexKeySpecsConflict","severity":"caution","meaning":"Создаваемый индекс имеет ту же спецификацию ключа (те же поля/порядок), что и существующий индекс, но другие параметры или другое имя. MongoDB не допускает двух конфликтующих определений индекса с одним ключом.","cause":"Предпринята попытка создать второй индекс по тем же полям с другим именем или другими параметрами (например, другим collation, partialFilterExpression, настройкой unique). Обычно происходит, когда определение индекса изменено в миграции/коде и запущено повторно.","fix":"Изучите существующие индексы: db.collection.getIndexes() Найдите конфликтующий индекс с тем же ключом. Чтобы перейти на нужные параметры, сначала удалите старый индекс (dropIndex), затем создайте его заново с новым определением. Если отличается только имя, продолжайте использовать существующий индекс; не создавайте лишнего второго определения.","tags":["index","getindexes","collation","unique","conflict","createindex"]},{"code":"166 CommandNotSupportedOnView","title":"CommandNotSupportedOnView","severity":"caution","meaning":"Команда не поддерживается для представления. Представления MongoDB доступны только для чтения и принимают лишь определённые операции чтения; запись и неподдерживаемые команды отклоняются.","cause":"Над представлением предприняты операции записи, такие как insert/update/delete, или определённые команды, не поддерживаемые для представлений (например, некоторые текстовые/геооперации, прямое создание индекса). Встречается, когда приложение пытается писать в представление, считая его коллекцией.","fix":"Выполняйте запись в базовую (исходную) коллекцию, на которой основано представление; представления предназначены только для чтения. Проверьте, поддерживается ли команда для представлений, и используйте поддерживаемые операции чтения/агрегации. Если нужна структура с возможностью записи, вместо представления спроектируйте материализованную коллекцию ($merge/$out).","tags":["view","read-only view","read only","aggregation","write","collection"]},{"code":"168 InvalidPipelineOperator","title":"InvalidPipelineOperator","severity":"caution","meaning":"В конвейере агрегации использован нераспознанный или недопустимый оператор/этап. MongoDB не смогла разрешить указанное имя оператора и отклонила конвейер.","cause":"Опечатка в имени оператора (например, '$groups' вместо '$group'), новый оператор, отсутствующий в используемой версии MongoDB, или выражение, использованное в неверном контексте. Причиной также может быть отсутствие префикса $ или оператор в недопустимой позиции.","fix":"Проверьте написание имён операторов/этапов и префикс $. Убедитесь, что оператор поддерживается версией MongoDB на сервере (новых операторов нет в старых версиях). Сравните конвейер с документированным синтаксисом; используйте выражение внутри правильного этапа (например, $expr, $project). Выделите этап и протестируйте его на небольшой выборке.","tags":["aggregation","pipeline","operator","syntax","version","expr"]},{"code":"118 NamespaceNotSharded","title":"NamespaceNotSharded","severity":"caution","meaning":"Операция, имеющая смысл только для шардированных коллекций, выполнена над коллекцией/пространством имён, которое ещё не шардировано. Команду не удалось применить, так как целевая коллекция не шардирована.","cause":"moveChunk, split или некоторые административные команды на основе ключа шардирования вызваны для коллекции без включённого шардинга. Обычно происходит потому, что коллекция не была шардирована с помощью shardCollection или выбрано неверное пространство имён.","fix":"Проверьте состояние шардинга коллекции: sh.status() или config.collections. Если операция действительно требует шардинга, сначала включите шардинг для базы данных (sh.enableSharding) и шардируйте коллекцию (sh.shardCollection с подходящим ключом шардирования). Иначе эти административные команды для нешардированных коллекций не нужны; убедитесь, что выбрано правильное пространство имён.","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":"Сервер Redis не смог разобрать поток байтов от клиента согласно RESP (Redis Serialization Protocol). Поскольку заявленное командой multibulk число элементов было недопустимым, соединение закрыто с ошибкой протокола.","cause":"Обычно вызвано тем, что клиент, не являющийся клиентом Redis (например, HTTP-запрос, приложение, подключённое не к тому порту), отправляет сырые данные на порт Redis, повреждённой/неполной клиентской библиотекой, ошибочным кадрированием в конвейере (pipeline) или огромными запросами, превышающими лимиты протокола (proto-max-bulk-len).","fix":"Убедитесь, что к порту Redis подключается только настоящий клиент Redis (без случайного трафика HTTP/других протоколов). Обновите клиентскую библиотеку и избегайте самописной/ручной генерации команд, нарушающей кадрирование RESP. Если отправляете очень большие значения, проверьте лимит proto-max-bulk-len и размер запроса. Убедитесь, что промежуточный прокси в сети не искажает данные.","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":"При выполнении Lua-скрипта (EVAL/EVALSHA/FCALL) указанное число ключей (numkeys) больше числа аргументов, фактически переданных команде. Redis отклоняет команду, так как не может разделить массивы KEYS и ARGV.","cause":"Синтаксис EVAL: 'EVAL script numkeys key [key ...] arg [arg ...]'. Значение numkeys рассчитано неверно (задано слишком большим), либо ожидаемые ключи так и не были добавлены в команду. Частая ошибка программирования.","fix":"Сделайте numkeys точно равным числу ключей, фактически переданных команде. Используйте обёртку EVAL клиентской библиотеки (обычно она принимает KEYS и ARGV отдельными списками), чтобы numkeys вычислялся автоматически. При вызове скрипта правильно разделяйте ключи в KEYS, а прочие параметры — в ARGV.","tags":["lua","eval","evalsha","numkeys","keys","script"]},{"code":"ERR WATCH inside MULTI is not allowed","title":"WATCH inside MULTI is not allowed","severity":"caution","meaning":"WATCH отправлен внутри открытого блока MULTI. Для оптимистической блокировки WATCH должен поставить ключи под наблюдение ДО начала транзакции; вызов после MULTI отклоняется. Ошибка не прерывает транзакцию; завершается ошибкой только WATCH, и ничего не отслеживается.","cause":"WATCH и MULTI записаны в коде в обратном порядке (сначала MULTI, затем WATCH); вызов WATCH внутри обёртки конвейера/транзакции клиентской библиотеки; либо использование соединения из пула, оставшегося в состоянии MULTI. В этом случае EXEC выполняется, даже если ключи, которые должны были отслеживаться, изменились; ожидаемая защита от конфликтов не срабатывает.","fix":"Правильный порядок: WATCH key -> (чтение значений) -> MULTI -> команды -> EXEC. Если EXEC возвращает nil, отслеживаемый ключ изменился; повторите операцию с начала. Используйте API WATCH/транзакций клиентской библиотеки (например, помощники watch + pipeline). Примечание: команды pub/sub, такие как SUBSCRIBE, внутри MULTI не отклоняются; они ставятся в очередь, и после EXEC соединение переходит в режим подписки; это тоже не рекомендуется — используйте для 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 выполнен на экземпляре-реплике (replica/slave). WAIT, ожидающий, пока запись дойдёт до заданного числа реплик, имеет смысл только на primary (master); на реплике его использовать нельзя.","cause":"Приложение ошибочно отправило WAIT через соединение с репликой. Обычно это происходит потому, что соединение направлено на реплику для чтения, или потому, что клиент всё ещё подключён к реплике после смены роли (failover).","fix":"Отправляйте WAIT только на основной узел. Убедитесь, что конфигурация клиента/прокси направляет запись и вызовы WAIT на master. Обнаруживайте смену ролей после отработки отказа и перенаправляйте соединения на новый primary (роль можно проверить командой ROLE).","tags":["wait","replica","master","write","failover","replication"]},{"code":"ERR Background append only file rewriting already in progress","title":"AOF rewrite already in progress","severity":"caution","meaning":"Предпринята попытка запустить перезапись AOF (Append Only File) (BGREWRITEAOF), но перезапись AOF уже выполняется. Две перезаписи AOF не могут выполняться одновременно.","cause":"Новый запрос на перезапись поступил, пока предыдущий BGREWRITEAOF (ручной или автоматический по триггеру auto-aof-rewrite-percentage) ещё выполнялся. На больших наборах данных перезапись AOF может занимать много времени и приводить к такому конфликту.","fix":"Дождитесь завершения текущей перезаписи; следите за полем aof_rewrite_in_progress в выводе INFO persistence. Если операция выполняется обоснованно, не отправляйте повторный запрос. Если конфликты часты, проверьте пороги автоматической перезаписи (auto-aof-rewrite-percentage/min-size) и производительность дискового ввода-вывода.","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":"Команда MIGRATE (обычно при перемещении слотов/ключей в Redis Cluster) столкнулась с ошибкой ввода-вывода или тайм-аутом при подключении к целевому экземпляру или чтении его ответа. Миграцию ключа не удалось безопасно завершить.","cause":"Сетевая задержка/сбой между исходным и целевым узлами, перегруженный или неотвечающий целевой узел, слишком короткий тайм-аут MIGRATE или большой ключ, который невозможно передать за отведённое время. Часто встречается при ребалансировке кластера (resharding).","fix":"Проверьте сетевую связность и задержку между исходным и целевым узлами. Задайте для MIGRATE более длинный тайм-аут (особенно для больших ключей). Проверьте состояние/нагрузку и память целевого узла. При перемещении больших ключей во время resharding выбирайте часы низкой нагрузки; при необходимости повторите миграцию.","tags":["migrate","cluster","resharding","timeout","network","i-o"]},{"code":"ERR Error compiling script (new function)","title":"Error compiling Lua script","severity":"caution","meaning":"Lua-скрипт, отправленный через EVAL, не удалось скомпилировать встроенным интерпретатором Lua в Redis. Он отклонён до начала выполнения, так как содержит синтаксическую ошибку.","cause":"Синтаксические ошибки в Lua-скрипте: незакрытые скобки/кавычки, опечатки в ключевых словах, отсутствующий 'end', недопустимые операторы или неподдерживаемая конструкция Lua. Причиной также может быть неверная конкатенация/экранирование скрипта на стороне клиента.","fix":"Исправьте синтаксис Lua, используя информацию о строке/символе из сообщения об ошибке (проверьте парность скобок, кавычек и 'end'). Проверьте скрипт, скомпилировав его в отдельной среде Lua. Убедитесь, что клиент правильно формирует скрипт (без ошибок экранирования/конкатенации). Заранее загружайте проверенные скрипты с помощью SCRIPT LOAD и вызывайте их через EVALSHA.","tags":["lua","eval","compilation","syntax","script load","script"]},{"code":"ERR FAILOVER requires connected replicas.","title":"FAILOVER requires connected replicas","severity":"caution","meaning":"FAILOVER выполнен для запуска скоординированной (плавной) отработки отказа, но отклонён, так как нет подходящей подключённой к primary реплики, способной принять роль. Без цели, которой можно передать роль, отработка отказа невозможна.","cause":"У primary в данный момент нет подключённых реплик (реплики остановлены, отключены от сети или не синхронизированы), либо указанная целевая реплика не подходит/не подключена. При вызове ручного FAILOVER (Redis 6.2+) топология репликации не удовлетворяет необходимому условию. Примечание: FAILOVER работает только с автономной репликацией (master/replica); в Redis Cluster на реплике используется CLUSTER FAILOVER.","fix":"Убедитесь, что существует хотя бы одна исправная, подключённая и актуальная реплика (проверьте connected_slaves и состояние синхронизации с помощью INFO replication). Устраните проблемы сети/доступа на репликах, чтобы они подключались к primary. Для отработки отказа на конкретную цель используйте FAILOVER TO host port и убедитесь, что эта реплика подходит.","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":"В SET (с параметрами EX/PX/EXAT/PXAT) передано недопустимое время истечения. Redis отклонил команду, так как не принимает бессмысленное время жизни, например ноль или отрицательное значение.","cause":"Отправка 0 или отрицательного значения с EX/PX либо неверный расчёт истечения в приложении, дающий недопустимое число. Команды, такие как SETEX/GETEX, аналогично возвращают эту ошибку для неположительных длительностей. Некоторые ошибки клиентов возникают из-за путаницы секунд и миллисекунд.","fix":"Всегда отправляйте время истечения как строго положительное целое число; EX ожидает секунды, PX — миллисекунды. Проверяйте расчёт длительности в приложении (он не должен давать отрицательный/нулевой результат) и не путайте единицы (секунды и миллисекунды). Чтобы немедленно удалить ключ, используйте DEL/UNLINK вместо установки истечения. Для абсолютного времени передавайте в EXAT/PXAT допустимую будущую метку времени Unix.","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":"При запуске узел Elasticsearch не смог привязать сокет для прослушивания транспортного (межузлового взаимодействия) или HTTP-порта. Обычно сопровождается подошибкой 'Address already in use', и узел не может запуститься.","cause":"Запрошенный порт (по умолчанию транспортный 9300, HTTP 9200) уже используется другим процессом; чаще всего предыдущий экземпляр того же узла не был корректно остановлен, конфликтует другой экземпляр/служба ES на том же хосте или неверно настроены network.host/port. Привязке также могут мешать ограничения прав/доступа.","fix":"Найдите, кто занимает порт (Linux: ss -ltnp | grep 9300 или lsof -i:9300), и завершите конфликтующий процесс либо назначьте узлу другой порт (transport.port / http.port). Убедитесь, что предыдущий процесс ES действительно завершился. Убедитесь, что network.host и диапазоны портов заданы правильно и доступны для привязки. Затем перезапустите узел.","tags":["transport","port","9300","bind","network.host","startup"]},{"code":"translog_corrupted_exception","title":"translog_corrupted_exception","severity":"danger","meaning":"Журнал транзакций (translog) шарда не удалось прочитать, так как он повреждён. Во время восстановления Elasticsearch обнаружил, что translog не прошёл проверку целостности, и шард не смог запуститься.","cause":"Файлы translog обычно повреждаются из-за внезапной потери питания, заполненного диска, ошибки файловой системы, отказа оборудования/диска или внешних операций с каталогом данных (копирование, ручное вмешательство). Причиной также могут быть частично завершённые записи при принудительной остановке (kill -9).","fix":"Сначала попробуйте восстановить данные из реплики или снимка; если существует исправная копия, пересоздание повреждённого шарда из неё — самый безопасный путь. Если другой копии нет и вы принимаете потерю данных, остановите узел и очистите повреждённый translog/данные с помощью bin/elasticsearch-shard remove-corrupted-data --index <index> --shard-id <n> (это безвозвратно теряет ещё не зафиксированные данные; старый инструмент elasticsearch-translog удалён в 7.0). Инструмент выводит команду POST _cluster/reroute (allocate_stale_primary, accept_data_loss: true), необходимую для размещения шарда после перезапуска узла. Перед операцией сделайте бэкап каталога данных. Проверьте состояние диска и свободное место.","tags":["translog","shard","recovery","corruption","snapshot","disk"]},{"code":"recovery_engine_exception","title":"recovery_engine_exception","severity":"danger","meaning":"Во время восстановления шарда произошла ошибка на уровне движка, и шард не удалось успешно открыть. Восстановление — это шаг, выполняемый при перезапуске узла, размещении реплики или восстановлении из снимка.","cause":"Исходная проблема во время восстановления: повреждённые сегменты Lucene или translog, недостаток места на диске, права доступа к файлам, обрыв сети между исходным и целевым узлами или проблемы версий/совместимости. Обычно оборачивает более конкретную подошибку (повреждение, ввод-вывод).","fix":"Изучите исходную (caused_by) ошибку, чтобы определить реальную проблему (повреждение, диск, сеть и т. д.). Проверьте место на диске и права доступа к файлам. Если размещение реплики не удаётся, выясните причину с помощью _cluster/allocation/explain и при необходимости инициируйте повторное размещение. При повреждении восстановите из исправной реплики/снимка. Если причина в сети, проверьте связность между узлами и настройки восстановления.","tags":["recovery","shard","engine","allocation","corruption","disk"]},{"code":"snapshot_in_progress_exception","title":"snapshot_in_progress_exception","severity":"caution","meaning":"Запрошенная операция отклонена, так как конфликтует с выполняющимся в данный момент снимком. Например, блокируется попытка удалить/закрыть индекс или изменить репозиторий во время создания снимка.","cause":"Пока снимок ещё выполнялся, была предпринята попытка удалить/закрыть индексы, входящие в этот снимок, удалить/изменить репозиторий или запустить другую конфликтующую операцию снимка/восстановления. Elasticsearch не допускает таких конфликтующих операций ради сохранения согласованности.","fix":"Дождитесь завершения текущего снимка: отслеживайте ход с помощью GET _snapshot/_status. Если операцию действительно нужно отменить, остановите её, удалив снимок: DELETE _snapshot/repo/snapshot_name (это прерывает выполняющийся снимок). После завершения или отмены снимка повторите исходную операцию (удаление индекса, изменение репозитория).","tags":["snapshot","repository","restore","index deletion","conflict","backup"]},{"code":"repository_verification_exception","title":"repository_verification_exception","severity":"danger","meaning":"При регистрации или проверке репозитория снимков не прошла проверка того, что все узлы имеют доступ к репозиторию. Поскольку проверка репозитория не пройдена, операции снимка/восстановления невозможно выполнять надёжно.","cause":"Репозиторий на общей файловой системе (fs) не смонтирован/недоступен по одному и тому же пути на всех узлах, path.repo отсутствует в конфигурации, недостаточно прав доступа к файлам либо у облачных/объектных хранилищ (S3, GCS, Azure) неверные учётные данные/сетевой доступ. Типичная причина — неспособность некоторых узлов писать в репозиторий.","fix":"Для репозитория на общей fs убедитесь, что path.repo определён в elasticsearch.yml на всех узлах и что один и тот же путь смонтирован с правами чтения/записи на каждом узле. Исправьте права доступа к файлам (пользователь elasticsearch). Для облачных репозиториев проверьте учётные данные (keystore), имя bucket и сетевые разрешения/права доступа. Повторно проверьте репозиторий с помощью 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":"Произошла ошибка на фазе reduce поиска. Координирующий узел столкнулся с проблемой при слиянии (reduce) частичных результатов/агрегаций с разных шардов и не смог сформировать итоговый результат.","cause":"Обычно при слиянии результатов превышается лимит памяти/ресурсов (например, очень большие агрегации, срабатывание circuit breaker), структура агрегации несовместима/недопустима либо частичный результат, возвращённый шардом, невозможно слить. Часто встречается в агрегациях с очень высокой кардинальностью.","fix":"Прочитайте подробности caused_by, чтобы определить первопричину. Облегчите тяжёлые агрегации: уменьшите size агрегаций terms с высокой кардинальностью, используйте пагинацию через агрегацию composite и сократите ненужные вложенные агрегации. Отслеживайте нагрузку на память и при необходимости разбейте запрос на меньшие части. Если срабатывает circuit breaker, снизьте стоимость запроса.","tags":["search","reduce","aggregation","coordinating node","memory","cardinality"]},{"code":"aggregation_execution_exception","title":"aggregation_execution_exception","severity":"caution","meaning":"Произошла ошибка при выполнении агрегации. Хотя определение агрегации было разобрано, во время выполнения при обработке целевых полей/значений возникла проблема.","cause":"Агрегация несовместима с типом поля (например, агрегация поля text без fielddata), недопустимые значения при сортировке/вычислении, конвейерная агрегация, ссылающаяся на несуществующий путь (bucket path), или неподдерживаемые сочетания типов полей. Причиной также могут быть ошибки выполнения в агрегациях на основе скриптов.","fix":"Изучите имя поля/агрегации в подробностях ошибки. Убедитесь, что агрегируемые поля подходящего типа (keyword/числовые вместо text; при необходимости используйте поля с включёнными doc_values). В конвейерных агрегациях убедитесь, что buckets_path соответствует фактическим именам корзин. Добавьте проверки на null/тип в агрегации со скриптами. Исправьте маппинг, задав подходящие типы полей.","tags":["aggregation","execution","fielddata","keyword","pipeline","mapping"]},{"code":"failed_node_exception","title":"failed_node_exception","severity":"danger","meaning":"Во время операции на уровне всего кластера (например, поиск, статистика, административная команда) определённый узел кластера не смог обработать запрос или ответить на него. Координирующий узел оборачивает сбой этого узла в данное исключение.","cause":"Узел аварийно завершился, выпал из сети, не смог ответить из-за перегрузки/пауз GC, проблем с диском/ресурсами или ошибки шарда на этом узле. Также может появляться временно во время перенастройки кластера (выход/присоединение узлов).","fix":"С помощью caused_by определите, на каком узле и почему произошёл сбой. Проверьте состояние узла: GET _cat/nodes, GET _nodes/stats (CPU, память, GC, диск). Если узел испытывает нехватку ресурсов, проверьте нагрузку/настройки heap; проверьте сетевую связность. Если узел покинул кластер, дождитесь его повторного присоединения и восстановления размещения шардов (GET _cluster/health).","tags":["node","cluster","network","gc","shard","resources"]},{"code":"elasticsearch_parse_exception","title":"elasticsearch_parse_exception","severity":"caution","meaning":"Elasticsearch столкнулся с общей ошибкой разбора при разборе тела запроса (JSON-запроса, настроек или конфигурации). Запрос структурно не соответствует ожидаемому формату.","cause":"Некорректный/недопустимый JSON (пропущенная запятая, незакрытая скобка), неуместные поля, неожиданные типы значений (например, текст там, где ожидается число) или недопустимое выражение времени/диапазона. Неправильная вложенность запроса DSL также приводит к этому исключению.","fix":"Проверьте тело запроса валидатором JSON; проверьте скобки, кавычки и запятые. Сверьтесь с документацией Elasticsearch DSL, что поля находятся на своих местах и имеют правильный тип. Используйте подсказку позиции/поля в сообщении об ошибке, чтобы исправить проблемную часть. Если используете клиентскую библиотеку, убедитесь, что сериализация даёт корректный JSON.","tags":["parse","json","dsl","query","parsing","request"]}]},"health/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"checks":[{"id":"version-uptime","group":"Общее","title":"Версия и время работы","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":"Статус экземпляра должен быть OPEN. Если startup_time неожиданно свежий (недавний запуск), возможно, был незапланированный перезапуск; разберитесь. Столбец version_full — 18c+; в 12c и ранее используйте столбец version.","severity":"safe"},{"id":"alertlog-errors","group":"Общее","title":"Недавние критические ошибки в 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":"За последние 24 часа не должно быть ошибок ORA-. Записи вроде ORA-00600, ORA-07445 и ORA-01578 (повреждение блоков) требуют немедленных действий.","severity":"danger"},{"id":"invalid-objects","group":"Общее","title":"Объекты INVALID","command":"SELECT owner, object_type, COUNT(*) AS invalid_count FROM dba_objects WHERE status = 'INVALID' GROUP BY owner, object_type ORDER BY invalid_count DESC;","expect":"В идеале объектов INVALID быть не должно. Если они есть, перекомпилируйте через EXEC DBMS_UTILITY.COMPILE_SCHEMA или utlrp.sql; невалидные объекты в SYS/SYSTEM — в приоритете.","severity":"caution"},{"id":"tablespace-usage","group":"Ёмкость","title":"Заполнение табличных пространств","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 выше 85% — порог предупреждения, выше 90% — критический. Табличные пространства с выключенным autoextend сильнее рискуют заполниться; добавьте файл данных или включите autoextend.","severity":"danger"},{"id":"largest-segments","group":"Ёмкость","title":"Самые большие сегменты (таблицы/индексы)","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":"Показывает сегменты, занимающие больше всего места. Неожиданный рост может указывать на необходимость архивирования/очистки или секционирования.","severity":"safe"},{"id":"undo-temp-usage","group":"Ёмкость","title":"Заполнение табличных пространств UNDO и TEMP","command":"SELECT ts.tablespace_name, ts.contents, ROUND(SUM(ss.used_blocks) * ts.block_size / 1024 / 1024, 1) AS used_mb FROM v$sort_segment ss JOIN (SELECT tablespace_name, contents, block_size FROM dba_tablespaces WHERE contents = 'TEMPORARY') ts ON ss.tablespace_name = ts.tablespace_name GROUP BY ts.tablespace_name, ts.contents, ts.block_size UNION ALL SELECT tablespace_name, 'UNDO', ROUND(SUM(bytes)/1024/1024, 1) FROM dba_undo_extents WHERE status = 'ACTIVE' GROUP BY tablespace_name;","expect":"Если использование TEMP постоянно упирается в потолок, есть большие операции sort/hash. Если активное пространство UNDO не обеспечивает retention, есть риск ORA-01555 (snapshot too old).","severity":"caution"},{"id":"sessions-vs-limit","group":"Ёмкость","title":"Число сессий относительно лимита","command":"SELECT resource_name, current_utilization, max_utilization, limit_value FROM v$resource_limit WHERE resource_name IN ('sessions','processes','transactions');","expect":"Если current_utilization приближается к limit_value (особенно выше 85%), новые подключения могут отклоняться. max_utilization показывает исторический пик; пересмотрите параметр processes.","severity":"caution"},{"id":"fra-usage","group":"Ёмкость","title":"Заполнение Flash Recovery Area (FRA)","command":"SELECT name, ROUND(space_limit/1024/1024/1024, 2) AS limit_gb, ROUND(space_used/1024/1024/1024, 2) AS used_gb, ROUND(space_used/space_limit*100, 1) AS pct_used FROM v$recovery_file_dest;","expect":"Когда FRA достигает 85%, в alert log пишется предупреждение; при 100% запись архивных журналов останавливается и база зависает (archiver hung, ORA-00257). Очистите бэкапы/архивы RMAN или увеличьте db_recovery_file_dest_size.","severity":"danger"},{"id":"buffer-cache-hit","group":"Производительность","title":"Коэффициент попаданий в buffer cache","command":"SELECT ROUND((1 - (phy.value - dir.value) / (con.value + cur.value)) * 100, 2) AS buffer_hit_ratio FROM v$sysstat con, v$sysstat cur, v$sysstat phy, v$sysstat dir WHERE con.name = 'consistent gets' AND cur.name = 'db block gets' AND phy.name = 'physical reads' AND dir.name = 'physical reads direct';","expect":"В OLTP-системах обычно ожидается выше 90%. Устойчиво низкие значения могут указывать на недостаточный размер SGA/buffer cache или плохую индексацию; интерпретируйте вместе с событиями ожидания, а не отдельно.","severity":"safe"},{"id":"top-sql","group":"Производительность","title":"Самые дорогие SQL-операторы (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":"Запросы с наибольшим суммарным временем выполнения. Кандидаты на настройку — с высоким sec_per_exec или очень большим executions; исследуйте их через AWR/SQL Tuning Advisor.","severity":"safe"},{"id":"long-active-sessions","group":"Производительность","title":"Долго выполняющиеся активные сессии","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":"Активные сессии с очень большим last_call_et (секунды) могут зависнуть или выполнять тяжёлый запрос. Столбец event показывает ожидаемый ресурс.","severity":"caution"},{"id":"blocking-sessions","group":"Производительность","title":"Блокирующие и ожидающие сессии (блокировки)","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":"Строк быть не должно. Блокирующие сессии вызывают задержки/взаимоблокировки в приложении; если blocker_sid долго удерживает блокировку (большое seconds_in_wait), оцените ситуацию с командой приложения и при необходимости выполните ALTER SYSTEM KILL SESSION.","severity":"danger"},{"id":"rman-last-backup","group":"Бэкап и восстановление","title":"Статус последнего успешного бэкапа RMAN","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":"У последнего бэкапа должны быть status = COMPLETED и start_time, достаточно свежий для вашей политики RPO (напр. за последние 24 часа). Записи FAILED или 'COMPLETED WITH WARNINGS' нужно расследовать.","severity":"danger"},{"id":"archivelog-mode","group":"Бэкап и восстановление","title":"Режим archivelog и статус archiver","command":"SELECT log_mode, force_logging, (SELECT COUNT(*) FROM v$archived_log WHERE first_time > SYSDATE - 1) AS logs_last_24h FROM v$database;","expect":"У продуктивной базы должен быть log_mode = ARCHIVELOG (иначе восстановление на момент времени невозможно). Если за последние 24 часа архивы не создавались, archiver может зависнуть.","severity":"danger"},{"id":"locked-expired-accounts","group":"Безопасность","title":"Заблокированные или просроченные учётные записи","command":"SELECT username, account_status, lock_date, expiry_date, profile FROM dba_users WHERE account_status != 'OPEN' ORDER BY username;","expect":"Учётные записи критичных приложений в состоянии LOCKED/EXPIRED вызывают неожиданные простои. Заблокированные/отключённые учётные записи по умолчанию (SCOTT, HR и т. д.) — это хорошо. EXPIRED(GRACE) указывает на скорое истечение пароля.","severity":"caution"},{"id":"privileged-users","group":"Безопасность","title":"Пользователи с высокими привилегиями (роль DBA)","command":"SELECT grantee, granted_role, admin_option FROM dba_role_privs WHERE granted_role IN ('DBA','PDB_DBA') AND grantee NOT IN ('SYS','SYSTEM') ORDER BY grantee;","expect":"Роль DBA должна быть только у необходимых, известных учётных записей. Неожиданные получатели или имеющие admin_option = YES — риск повышения привилегий; пересмотрите их по принципу минимальных привилегий. Для учётных записей с привилегиями SYSDBA/SYSOPER проверьте также: SELECT username, sysdba, sysoper FROM v$pwfile_users;","severity":"caution"},{"id":"default-password-users","group":"Безопасность","title":"Открытые учётные записи с неизменёнными паролями по умолчанию","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":"У учётных записей с has_default_password = YES пароль нужно сменить немедленно или заблокировать их (ALTER USER ... ACCOUNT LOCK). Для политики срока действия пароля проверьте также PASSWORD_LIFE_TIME в DBA_PROFILES.","severity":"caution"},{"id":"dataguard-lag","group":"Репликация","title":"Отставание standby в Data Guard","command":"SELECT name, value, unit, time_computed FROM v$dataguard_stats WHERE name IN ('apply lag','transport lag');","expect":"apply lag и transport lag должны быть как можно ближе к 0. Отставание порядка минут/часов указывает на проблему сети или MRP (Managed Recovery Process); цель отработки отказа под угрозой.","severity":"danger"},{"id":"dataguard-mrp","group":"Репликация","title":"Статус процесса применения на standby (MRP)","command":"SELECT name, role, action, thread#, sequence#, block# FROM v$dataguard_process WHERE name LIKE 'MRP%' OR role LIKE 'RFS%' ORDER BY name;","expect":"На физическом standby должен быть виден процесс MRP0 с action APPLYING_LOG или WAIT_FOR_LOG. Если MRP нет, применение остановлено; если нет RFS, журналы не приходят с primary. v$dataguard_process — 12.2+; в 12.1 и ранее используйте (ныне устаревшее) v$managed_standby (столбцы process, status).","severity":"danger"}]},"health/postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"checks":[{"id":"version-uptime","group":"Общее","title":"Версия и время работы","command":"SELECT version() AS pg_version, pg_postmaster_start_time() AS started_at, date_trunc('second', now() - pg_postmaster_start_time()) AS uptime;","expect":"Показывает версию и время запуска. Если uptime неожиданно мал (недавний запуск), возможно, был незапланированный перезапуск или сбой; проверьте журналы.","severity":"safe"},{"id":"recent-errors","group":"Общее","title":"Статистика контрольных точек","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":"Если число запрошенных контрольных точек (num_requested / checkpoints_req) велико относительно плановых (num_timed / checkpoints_timed), WAL заполняется слишком быстро; увеличьте max_wal_size. В PG17 счётчики перенесены из pg_stat_bgwriter в pg_stat_checkpointer.","severity":"caution"},{"id":"table-bloat","group":"Общее","title":"Раздутые таблицы и доля мёртвых кортежей","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":"Таблицы с высоким dead_pct (напр. выше 20%) раздуты; это влияет на производительность VACUUM и использование диска. Если last_autovacuum очень старый, пересмотрите настройки autovacuum или выполните ручной VACUUM (ANALYZE).","severity":"caution"},{"id":"database-size","group":"Ёмкость","title":"Размеры баз данных","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":"Показывает размеры баз данных. Оценивайте неожиданный рост вместе с ёмкостью диска (df -h); при заполнении диска PostgreSQL останавливает операции записи.","severity":"danger"},{"id":"largest-tables","group":"Ёмкость","title":"Самые большие таблицы (включая индексы)","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":"Показывает таблицы, занимающие больше всего места. Если index_toast_size намного больше размера таблицы, возможно, есть лишние/неиспользуемые индексы; рассмотрите архивирование или секционирование.","severity":"safe"},{"id":"temp-files","group":"Ёмкость","title":"Использование временных файлов","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":"Высокие temp_files/temp_bytes указывают, что work_mem недостаточен и операции sort/hash сбрасываются на диск. Рассмотрите увеличение work_mem или оптимизацию запросов.","severity":"caution"},{"id":"connections-vs-limit","group":"Ёмкость","title":"Число подключений относительно 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":"Считаются только клиентские подключения (фильтр backend_type = 'client backend' нужен, так как pg_stat_activity включает и фоновые процессы). При pct_used выше 80% новые подключения рискуют быть отклонены (особенно по достижении superuser_reserved_connections). Рекомендуется пул подключений (PgBouncer).","severity":"caution"},{"id":"wal-directory-size","group":"Ёмкость","title":"Размер каталога WAL и удержание слотами репликации","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":"Неактивные (active = false) слоты репликации приводят к накоплению WAL и заполнению каталога pg_wal. Если retained_wal велик, проверьте владельца слота (slot_type, database, plugin): active = false может означать и временно отключённый standby или слот логического подписчика/CDC (напр. Debezium). Если слот действительно не используется, удалите его через pg_drop_replication_slot; standby, зависевший от удалённого слота, придётся пересоздать, а сторону подписчика/CDC — ресинхронизировать. Для постоянной защиты задайте верхнюю границу через max_slot_wal_keep_size (PG13+).","severity":"danger"},{"id":"cache-hit-ratio","group":"Производительность","title":"Коэффициент попаданий в buffer cache","command":"SELECT sum(heap_blks_read) AS heap_read, sum(heap_blks_hit) AS heap_hit, round(sum(heap_blks_hit) * 100.0 / NULLIF(sum(heap_blks_hit) + sum(heap_blks_read), 0), 2) AS cache_hit_ratio FROM pg_statio_user_tables;","expect":"Для OLTP-нагрузок цель обычно выше 99%. Стабильно низкое значение может указывать, что shared_buffers слишком мал или рабочий набор не помещается в память.","severity":"safe"},{"id":"top-queries","group":"Производительность","title":"Самые дорогие запросы (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 должен быть в shared_preload_libraries (требуется перезапуск) и установлен в базе (CREATE EXTENSION pg_stat_statements). Имена total_exec_time/mean_exec_time — для PG13+; в PG12 используются total_time/mean_time. Запросы с наибольшим суммарным временем — кандидаты на настройку; для запросов с высоким mean_ms выполните EXPLAIN ANALYZE.","severity":"safe"},{"id":"long-running-queries","group":"Производительность","title":"Долго выполняющиеся активные запросы","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":"Активные запросы дольше 1 минуты могут зависнуть или быть тяжёлыми. wait_event показывает, чего ожидает запрос. Этот запрос выводит только активные запросы; сессии 'idle in transaction' (мешающие VACUUM удалять мёртвые кортежи) проверяйте отдельно через state = 'idle in transaction' и xact_start.","severity":"caution"},{"id":"blocking-locks","group":"Производительность","title":"Блокирующие сессии и ожидающие блокировки","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":"Строк быть не должно. Блокирующие сессии (blocking_pid) вызывают задержки и тайм-ауты в приложении; расследуйте долго блокирующую транзакцию и при необходимости завершите её через pg_terminate_backend.","severity":"danger"},{"id":"archiver-status","group":"Бэкап и восстановление","title":"Состояние архивирования WAL (pg_stat_archiver)","command":"SELECT archived_count, last_archived_wal, last_archived_time, failed_count, last_failed_wal, last_failed_time, now() - last_archived_time AS since_last_archive FROM pg_stat_archiver;","expect":"failed_count должен быть 0, а last_failed_time — старше last_archived_time. Если since_last_archive очень велик, архивирование остановилось (сбой archive_command); цепочка бэкапов для PITR нарушена.","severity":"danger"},{"id":"last-basebackup","group":"Бэкап и восстановление","title":"Свежесть последнего бэкапа и архива WAL","command":"-- Base backup time is not stored in the database; read it from the backup tool: pgbackrest info / barman list-backup <server>\nSELECT pg_is_in_recovery() AS in_recovery, last_archived_wal, last_archived_time, now() - last_archived_time AS since_last_archive, failed_count, last_failed_time FROM pg_stat_archiver;","expect":"Проверьте время последнего базового бэкапа по инструменту бэкапа (журналы заданий pgBackRest / Barman / pg_basebackup); оно должно быть достаточно свежим для вашей политики RPO. Для PITR архив WAL также должен быть непрерывным: если since_last_archive велик или failed_count растёт, цепочка архива может быть нарушена.","severity":"danger"},{"id":"locked-expired-roles","group":"Безопасность","title":"Просроченные или истекающие login-роли","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":"Роли приложений с истёкшим rolvaliduntil не могут войти, что вызывает простой. Заранее планируйте обновление паролей, истекающих в ближайшие 7 дней.","severity":"caution"},{"id":"superuser-roles","group":"Безопасность","title":"Роли с высокими привилегиями (SUPERUSER / привилегии создания)","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 должен быть только у необходимых, известных учётных записей. Роли с rolbypassrls = true обходят защиту на уровне строк (RLS); неожиданные высокие привилегии следует отозвать по принципу минимальных привилегий.","severity":"caution"},{"id":"hba-trust-auth","group":"Безопасность","title":"Слабые правила аутентификации (trust)","command":"SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method IN ('trust','password') ORDER BY line_number;","expect":"auth_method = trust разрешает доступ без пароля и опасен в продакшене; 'password' передаёт пароль открытым текстом. Замените их на scram-sha-256. (Требуется PG 10+.)","severity":"danger"},{"id":"replication-lag","group":"Репликация","title":"Отставание потоковой репликации (на стороне primary)","command":"SELECT client_addr, state, sync_state, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)) AS sent_lag, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_lag, write_lag, flush_lag, replay_lag AS replay_time_lag FROM pg_stat_replication ORDER BY client_addr;","expect":"Выполняйте на primary. state должен быть streaming, а replay_lag (байты/время) — малым. Если standby не отображается вовсе, соединение разорвано; если sync_state = async, синхронная репликация работает не так, как ожидается.","severity":"danger"},{"id":"replica-lag-standby","group":"Репликация","title":"Отставание репликации (на стороне standby)","command":"SELECT pg_is_in_recovery() AS in_recovery, CASE WHEN pg_is_in_recovery() THEN date_trunc('second', now() - pg_last_xact_replay_timestamp()) ELSE NULL END AS replication_delay, pg_last_wal_receive_lsn() AS receive_lsn, pg_last_wal_replay_lsn() AS replay_lsn;","expect":"Выполняйте на сервере standby. in_recovery должен быть true. Если replication_delay — минуты/часы, возможно отставание применения; однако это значение растёт и при отсутствии записи на primary (ложная тревога), поэтому интерпретируйте его вместе с разницей LSN; если разрыв между receive_lsn и replay_lsn велик, воспроизведение WAL не успевает.","severity":"danger"}]},"health/mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"checks":[{"id":"version-uptime","group":"Общее","title":"Версия и время работы","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":"Показывает версию и время работы в секундах. Если uptime_seconds неожиданно мал, возможно, был незапланированный перезапуск или сбой; проверьте журнал ошибок.","severity":"safe"},{"id":"error-log-status","group":"Общее","title":"Недавние записи журнала ошибок (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":"Показывает недавние ошибки и предупреждения (требуется MySQL 8.0+). Расследуйте записи с prio = 'Error' (особенно ошибки InnoDB, блокировок и диска). В старых версиях проверяйте файл log_error сервера.","severity":"danger"},{"id":"crashed-tables","group":"Общее","title":"Повреждённые таблицы / таблицы для проверки","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":"Выводит движок и время последнего обновления каждой таблицы. Таблицы MyISAM могут повреждаться; при сомнениях проверьте целостность через CHECK TABLE ... или mysqlcheck. Для критичных данных рекомендуется InnoDB.","severity":"caution"},{"id":"schema-sizes","group":"Ёмкость","title":"Размеры схем (баз данных)","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":"Показывает размеры схем и освобождаемое пространство data_free. Оценивайте вместе с ёмкостью диска; если free_mb велик, пространство можно вернуть через OPTIMIZE TABLE.","severity":"danger"},{"id":"largest-tables","group":"Ёмкость","title":"Самые большие таблицы (включая data_free / фрагментацию)","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":"Показывает самые большие таблицы. Таблицы с высоким data_free_mb фрагментированы; пространство можно вернуть через OPTIMIZE TABLE. Для очень больших таблиц рассмотрите секционирование.","severity":"safe"},{"id":"innodb-tablespace","group":"Ёмкость","title":"Заполнение табличных пространств InnoDB и временного пространства","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":"Показывает заполненность файлов данных InnoDB. Если innodb_file_per_table выключен, ibdata1 постоянно растёт и никогда не уменьшается. При раздувании temp tablespace проверьте также вывод SHOW ENGINE INNODB STATUS.","severity":"caution"},{"id":"connections-vs-limit","group":"Ёмкость","title":"Число подключений и лимит max_connections","command":"SELECT (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected') AS connected, (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'max_connections') AS max_connections, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Max_used_connections') AS max_used, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Aborted_connects') AS aborted_connects;","expect":"Если max_used приближается к max_connections (выше 85%), есть риск ошибок 'Too many connections'. Высокий aborted_connects указывает на проблемы аутентификации/сети; используйте пул подключений.","severity":"caution"},{"id":"binlog-size","group":"Ёмкость","title":"Файлы бинарного журнала и общий размер","command":"SHOW BINARY LOGS;","expect":"Выводит файлы binlog и их размеры. Если общий размер давит на дисковое пространство, пересмотрите binlog_expire_logs_seconds (expire_logs_days до 8.0; эта переменная устарела в 8.0 и удалена в 8.4); при использовании репликации не удаляйте журналы, пока реплики их не прочитали.","severity":"caution"},{"id":"buffer-pool-hit","group":"Производительность","title":"Коэффициент попаданий InnoDB buffer pool","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 обычно целевое значение выше 99%. Если он остаётся низким, innodb_buffer_pool_size недостаточен; рассмотрите увеличение до ~70-80% RAM на выделенном сервере (меньше на общем).","severity":"safe"},{"id":"top-queries","group":"Производительность","title":"Самые дорогие запросы (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":"Схема sys и performance_schema должны быть включены. Запросы с наибольшим total_latency — кандидаты на настройку. Если rows_examined_avg намного больше rows_sent_avg, не хватает индекса; исследуйте через EXPLAIN.","severity":"safe"},{"id":"long-running-queries","group":"Производительность","title":"Долго выполняющиеся активные запросы","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":"Запросы дольше 60 секунд могут зависнуть или быть тяжёлыми. Столбец state показывает, чего ожидает запрос. Долгие сессии в состоянии 'Sleep' тоже исчерпывают пул подключений; при необходимости используйте KILL <id>.","severity":"caution"},{"id":"blocking-locks","group":"Производительность","title":"Блокирующие сессии и ожидающие блокировки","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":"Строк быть не должно. Блокирующая транзакция (blocking_thread) вызывает задержки в приложении и ошибки lock wait timeout. Расследуйте долго блокирующую транзакцию и при необходимости завершите её через KILL. (Синтаксис MySQL 8.0.)","severity":"danger"},{"id":"binlog-enabled","group":"Бэкап и восстановление","title":"Параметры бинарного журнала и восстановления","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 должен быть ON. Для надёжности рекомендуются sync_binlog = 1 и innodb_flush_log_at_trx_commit = 1 (иначе при сбое транзакции могут потеряться). binlog_format = ROW — самый безопасный для репликации.","severity":"danger"},{"id":"last-backup-note","group":"Бэкап и восстановление","title":"Время последнего бэкапа (binlog / индикатор инструмента)","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 сам не отслеживает статус бэкапов; проверяйте время последнего успешного бэкапа по журналам mysqldump / MySQL Shell dump / Percona XtraBackup (mysqlpump объявлен устаревшим в 8.0.34 и удалён в 8.4). Бэкап должен быть настолько свежим, насколько требует ваша политика RPO (напр. за последние 24 часа).","severity":"danger"},{"id":"locked-expired-accounts","group":"Безопасность","title":"Заблокированные учётные записи или с истёкшими паролями","command":"SELECT user, host, account_locked, password_expired, password_lifetime FROM mysql.user ORDER BY user, host;","expect":"Учётные записи критичных приложений с account_locked = 'Y' или password_expired = 'Y' вызывают простои. Если password_lifetime равен NULL и глобальное истечение выключено, пароль никогда не истекает; это может быть пробелом в политике.","severity":"caution"},{"id":"anonymous-nopassword-users","group":"Безопасность","title":"Анонимные пользователи и пользователи без пароля","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":"Строк быть не должно. Анонимные пользователи (user = '') и учётные записи без пароля — серьёзная брешь в безопасности; удалите их через mysql_secure_installation или задайте надёжные пароли.","severity":"danger"},{"id":"super-privilege-users","group":"Безопасность","title":"Пользователи с высокими привилегиями (SUPER / GRANT)","command":"SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE privilege_type IN ('SUPER','GRANT OPTION','SHUTDOWN','FILE','CREATE USER') ORDER BY grantee, privilege_type;","expect":"Привилегии вроде SUPER, FILE и GRANT OPTION должны быть только у необходимых, известных учётных записей. Неожиданные получатели — риск повышения привилегий; особое внимание уделяйте учётным записям с высокими привилегиями, заданным с % (любой хост).","severity":"caution"},{"id":"replica-status","group":"Репликация","title":"Статус и отставание реплики (standby)","command":"SHOW REPLICA STATUS;","expect":"Выполняйте на реплике (MySQL 8.0.22+). Replica_IO_Running и Replica_SQL_Running оба должны быть 'Yes'. Seconds_Behind_Source должен быть малым; если он NULL, репликация остановлена. Проверьте поле Last_Error. (В старых версиях — SHOW SLAVE STATUS.)","severity":"danger"},{"id":"replication-threads","group":"Репликация","title":"Статус подключения и применения репликации (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 и sql_state должны быть 'ON'. Если io_error/sql_error заполнены, репликация завершилась сбоем и остановлена; в multi-source-конфигурациях каждый channel_name должен быть исправен по отдельности.","severity":"danger"}]},"health/mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"checks":[{"id":"version-uptime","group":"Общее","title":"Версия и время работы","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":"Проверьте версию и уровень патчей. Если uptime очень мал (меньше нескольких часов), возможно, был неожиданный перезапуск; просмотрите журнал ошибок SQL Server.","severity":"safe"},{"id":"database-states","group":"Общее","title":"Базы данных в проблемном состоянии (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":"Все продуктивные базы должны быть ONLINE. SUSPECT, RECOVERY_PENDING или EMERGENCY требуют немедленных действий; запустите восстановление и просмотрите журнал ошибок.","severity":"danger"},{"id":"recent-errors","group":"Общее","title":"Недавние значимые ошибки (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":"Просмотрите недавние записи об исключениях/ошибках. Часто повторяющиеся ошибки 823/824/825 (согласованность I/O) указывают на повреждение диска. Подробности читайте также в журнале ошибок на сервере через xp_readerrorlog.","severity":"caution"},{"id":"data-log-file-usage","group":"Ёмкость","title":"Заполнение файлов данных и журнала","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":"Выполняйте в каждой базе. Если занятое пространство очень близко к выделенному (85%+) и автоприращение ограничено, запланируйте рост или добавьте диск. Если файл журнала постоянно заполнен, пересмотрите настройки бэкапа/модели восстановления.","severity":"caution"},{"id":"largest-tables","group":"Ёмкость","title":"Самые большие таблицы (размер и число строк)","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":"Определите таблицы, занимающие больше всего места. Неожиданный рост может указывать на необходимость архивирования, обслуживания индексов или секционирования.","severity":"safe"},{"id":"tempdb-usage","group":"Ёмкость","title":"Использование пространства tempdb","command":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_objects_mb, SUM(internal_object_reserved_page_count)*8/1024 AS internal_objects_mb, SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb, SUM(unallocated_extent_page_count)*8/1024 AS free_mb FROM sys.dm_db_file_space_usage;","expect":"Если в tempdb вот-вот закончится свободное место (мало unallocated), запросы могут завершаться ошибкой. Если version store постоянно растёт, есть долгие открытые транзакции; расследуйте через sys.dm_tran_active_snapshot_database_transactions.","severity":"caution"},{"id":"connection-count","group":"Ёмкость","title":"Число и распределение подключений","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":"Отслеживайте число подключений по приложениям/базам. Необычно большое число подключений от одного приложения может указывать на утечку в пуле подключений. Не приближайтесь к лимиту max worker threads.","severity":"safe"},{"id":"vlf-count","group":"Ёмкость","title":"Число VLF в файле журнала (фрагментация)","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":"Большое число VLF (больше нескольких сотен, напр. 500+) означает, что файл журнала рос очень малыми приращениями; это замедляет восстановление и бэкап. Сожмите журнал и увеличьте его заново одним большим шагом.","severity":"caution"},{"id":"buffer-cache-ple","group":"Производительность","title":"Buffer Cache Hit Ratio и 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 в идеале должен быть 99%+ (hit_ratio_raw / hit_ratio_base). Если Page Life Expectancy стабильно низкий (эмпирическое правило: ниже 300 s на 4 GB на узел NUMA), есть нехватка памяти; нужно больше RAM или оптимизация запросов.","severity":"caution"},{"id":"expensive-queries","group":"Производительность","title":"Самые дорогие запросы (CPU и чтения)","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":"Определите самые дорогие запросы по CPU и логическим чтениям. Запросы с большим числом чтений — признак отсутствующих индексов; просмотрите их планы выполнения и добавьте индексы или перепишите запросы.","severity":"safe"},{"id":"blocking-sessions","group":"Производительность","title":"Блокирующие сессии","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":"Блокирующая сессия (blocking_session_id <> 0) означает, что запросы заставляют друг друга ждать. Долгая блокировка вызывает тайм-ауты в приложении; найдите головной блокировщик (head blocker) и рассмотрите завершение долгой транзакции.","severity":"danger"},{"id":"wait-stats","group":"Производительность","title":"Основная статистика ожиданий (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":"Интерпретируйте преобладающие типы ожиданий: PAGEIOLATCH = узкое место дискового I/O, CXPACKET/CXCONSUMER = параллелизм, LCK_M = блокировки, WRITELOG = диск журнала, RESOURCE_SEMAPHORE = память. Высокий процент signal wait указывает на нехватку CPU.","severity":"safe"},{"id":"last-backup","group":"Бэкап и восстановление","title":"Время последних бэкапов (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":"У каждой продуктивной базы должен быть свежий полный бэкап. Если last_full_backup равен NULL или очень старый (за пределами RPO), задание бэкапа завершается сбоем. В модели восстановления FULL без бэкапов журнала файл журнала постоянно растёт.","severity":"danger"},{"id":"recovery-model","group":"Бэкап и восстановление","title":"Согласованность моделей восстановления","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":"Критичные базы должны использовать FULL для восстановления на момент времени. log_reuse_wait_desc = LOG_BACKUP означает, что ожидается бэкап журнала; ACTIVE_TRANSACTION указывает на долгую транзакцию.","severity":"caution"},{"id":"sysadmin-members","group":"Безопасность","title":"Члены роли sysadmin","command":"SELECT p.name AS login_name, p.type_desc AS type, p.is_disabled AS disabled FROM sys.server_role_members rm JOIN sys.server_principals r ON rm.role_principal_id = r.principal_id JOIN sys.server_principals p ON rm.member_principal_id = p.principal_id WHERE r.name = N'sysadmin' ORDER BY p.name;","expect":"Членство в sysadmin нужно свести к минимуму. Неожиданные login (особенно учётные записи приложений или отдельных пользователей) — риск повышения привилегий; применяйте принцип минимальных привилегий.","severity":"danger"},{"id":"sa-and-password-policy","group":"Безопасность","title":"Учётная запись sa и login с отключённой политикой паролей","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":"Учётную запись sa следует отключить или переименовать. SQL login с is_policy_checked = 0 несут риск слабого/устаревшего пароля; включите политику паролей (CHECK_POLICY).","severity":"caution"},{"id":"alwayson-sync","group":"Репликация","title":"Состояние синхронизации и очереди Always On","command":"SELECT ag.name AS availability_group, ar.replica_server_name AS replica_server, DB_NAME(drs.database_id) AS database_name, drs.synchronization_state_desc AS sync_state, drs.synchronization_health_desc AS health, drs.log_send_queue_size AS log_send_queue_kb, drs.redo_queue_size AS redo_queue_kb FROM sys.dm_hadr_database_replica_states drs JOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id JOIN sys.availability_groups ag ON ar.group_id = ag.group_id ORDER BY ag.name, replica_server;","expect":"synchronization_state_desc должен быть SYNCHRONIZED (синхронно) или SYNCHRONIZING (асинхронно), а synchronization_health — HEALTHY. Если log_send_queue или redo_queue растут, реплика отстаёт; риск потери данных/RTO при отработке отказа возрастает.","severity":"caution"}]},"health/mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"checks":[{"id":"version-uptime","group":"Общее","title":"Версия и время работы","command":"db.serverStatus().version; db.serverStatus().uptime","expect":"Убедитесь, что версия поддерживается и обновлена патчами. uptime — в секундах; если он очень мал (несколько тысяч секунд), был неожиданный перезапуск; просмотрите журналы mongod.","severity":"safe"},{"id":"replset-status","group":"Общее","title":"Состояния членов набора реплик (health)","command":"rs.status().members.forEach(function(m) { print(m.name + \" | \" + m.stateStr + \" | health=\" + m.health); })","expect":"У каждого члена должен быть health=1, и PRIMARY должен быть ровно один. health=0 или состояния (not reachable/healthy), DOWN и RECOVERING указывают на сбой. Если PRIMARY больше одного или нет вовсе, есть проблема с выборами.","severity":"danger"},{"id":"dbstats-sizes","group":"Ёмкость","title":"Размеры баз данных (data / storage / index)","command":"db.stats(1024*1024)","expect":"Отслеживайте dataSize, storageSize и indexSize (MB). Если storageSize намного больше dataSize, возможна фрагментация. Размещение indexSize в пределах RAM (кэш WiredTiger) критично для производительности.","severity":"safe"},{"id":"disk-space","group":"Ёмкость","title":"Использование дискового пространства (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":"Если отношение fsUsedSize к fsTotalSize превышает 85%, диск вот-вот заполнится; MongoDB останавливает запись при заполненном диске. Запланируйте добавление диска или архивирование данных.","severity":"caution"},{"id":"largest-collections","group":"Ёмкость","title":"Самые большие коллекции","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":"Определите коллекции, занимающие больше всего места. Неожиданный рост может указывать на необходимость архивирования, TTL-индекса или шардинга. Пересмотрите коллекции, у которых размер индексов превышает размер данных.","severity":"safe"},{"id":"connections","group":"Ёмкость","title":"Число подключений (current / available)","command":"db.serverStatus().connections","expect":"Если current приближается к available, лимит подключений вот-вот будет достигнут и новые подключения будут отклоняться. Проверьте настройки пула подключений приложения и возможные утечки.","severity":"caution"},{"id":"wiredtiger-cache","group":"Производительность","title":"Использование кэша WiredTiger и вытеснение","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":"Вытеснение запускается, когда используемая память превышает 80% максимального кэша; выше 95% потоки приложения принудительно участвуют в вытеснении и производительность падает. Высокая доля грязных страниц указывает на давление записи.","severity":"caution"},{"id":"slow-queries-profiler","group":"Производительность","title":"Медленные запросы (профилировщик) и 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":"Эта проверка только читает: сначала показывает статус профилировщика (was: 0 — выключен, 1 — только медленные операции, 2 — все), затем выводит записанные медленные операции. Если профилировщик выключен (was: 0), system.profile остаётся пустой; для диагностики включите его отдельным шагом через db.setProfilingLevel(1, {slowms: 100}) и отключите через db.setProfilingLevel(0) по завершении. Запросы с COLLSCAN в planSummary выполняют полное сканирование коллекции, и им не хватает индекса. Запросы с высоким millis оптимизируйте индексацией. Профилировщик добавляет накладные расходы, поэтому в продакшене используйте его осторожно.","severity":"caution"},{"id":"long-running-ops","group":"Производительность","title":"Долгие операции (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":"Активные операции с высоким secs_running могут вызывать блокировки. При необходимости завершайте их через db.killOp(opid). Операции с waitingForLock=true ожидают блокировку; расследуйте корневую операцию.","severity":"danger"},{"id":"oplog-window","group":"Бэкап и восстановление","title":"Окно oplog (история репликации)","command":"rs.printReplicationInfo()","expect":"Окно oplog (настроенный размер oplog и длина журнала в часах) должно быть достаточно длинным. Если окно короткое (несколько часов), медленный secondary может уйти в полную ресинхронизацию (initial sync). Стратегии бэкапа и восстановления на момент времени зависят от oplog.","severity":"caution"},{"id":"backup-note","group":"Бэкап и восстановление","title":"Статус бэкапа и заметка о последнем бэкапе","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 нет встроенного бэкапа по расписанию; используются mongodump, снимки файловой системы, Ops Manager или PBM. Проверяйте время последнего успешного бэкапа и тест восстановления во внешнем инструменте. В наборе реплик окно oplog (oplogWindowHours) ограничивает глубину PITR на основе oplog; оно не должно быть короче интервала бэкапов. Убедитесь, что цели RPO/RTO выполняются.","severity":"danger"},{"id":"users-roles","group":"Безопасность","title":"Список пользователей и ролей","command":"db.getSiblingDB('admin').system.users.find({}, {user: 1, db: 1, roles: 1, _id: 0}).forEach(printjson)","expect":"Просмотрите определённых пользователей и их роли. Неожиданные учётные записи или слишком широкие роли (root, __system, dbOwner) — риск повышения привилегий. Применяйте принцип минимальных привилегий.","severity":"caution"},{"id":"auth-enabled","group":"Безопасность","title":"Включена ли аутентификация (authorization)","command":"db.getSiblingDB('admin').runCommand({getCmdLineOpts: 1}).parsed.security","expect":"security.authorization должен быть 'enabled'. Если он отключён (или нет блока security), база открыта для доступа без аутентификации; это серьёзная брешь в безопасности, включите немедленно.","severity":"danger"},{"id":"secondary-lag","group":"Репликация","title":"Отставание репликации secondary","command":"rs.printSecondaryReplicationInfo()","expect":"Значение 'replication lag' для каждого secondary должно быть малым (несколько секунд). Если отставание велико (минуты), secondary не справляется с нагрузкой или есть проблема сети/диска; риск потери данных и несогласованного чтения при отработке отказа возрастает.","severity":"caution"},{"id":"member-health","group":"Репликация","title":"Здоровье членов и состояние выборов","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":"Все члены должны быть исправны (health=1) и в ожидаемом состоянии (PRIMARY/SECONDARY). Сообщение об ошибке в lastHeartbeatMessage указывает на сетевое разделение или недоступный член. Потеря большинства останавливает запись.","severity":"caution"}]},"health/redis.json":{"engineId":"redis","engine":"Redis","order":6,"checks":[{"id":"server-version-uptime","group":"Общее","title":"Версия и время работы","command":"redis-cli INFO server | grep -E \"redis_version|uptime_in_days|redis_mode|os\"","expect":"redis_version должна быть поддерживаемым релизом с патчами. Если uptime_in_days очень мал, был неожиданный перезапуск; redis_mode (standalone/sentinel/cluster) подтверждает конфигурацию.","severity":"safe"},{"id":"persistence-status","group":"Общее","title":"Состояние персистентности (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 и aof_last_write_status должны быть 'ok'. Значение 'err' указывает на проблему с диском/правами или заполненный диск и несёт риск потери данных. loading:1 означает, что сервер ещё загружает данные.","severity":"danger"},{"id":"memory-usage","group":"Ёмкость","title":"Использование памяти (used_memory / maxmemory)","command":"redis-cli INFO memory | grep -E \"used_memory_human|used_memory:|maxmemory_human|maxmemory:|maxmemory_policy\"","expect":"Если used_memory приближается к maxmemory (85%+), есть риск вытеснения или OOM. Если maxmemory=0, лимита нет, и Redis может занять всю RAM и уронить систему; всегда задавайте лимит и подходящий maxmemory_policy.","severity":"caution"},{"id":"memory-fragmentation","group":"Ёмкость","title":"Коэффициент фрагментации памяти","command":"redis-cli INFO memory | grep -E \"mem_fragmentation_ratio|mem_allocator\"","expect":"mem_fragmentation_ratio в идеале между 1.0 и 1.5. Выше 1.5 — высокая фрагментация (потерянная память). Ниже 1.0 — Redis выгружается в swap на диск (серьёзная проблема производительности); проверьте RAM системы.","severity":"caution"},{"id":"evicted-keys","group":"Ёмкость","title":"Вытесненные ключи (evicted_keys)","command":"redis-cli INFO stats | grep -E \"evicted_keys|expired_keys|keyspace_misses\"","expect":"Если evicted_keys постоянно растёт, Redis достиг лимита памяти и отбрасывает данные; это может привести к нежелательной потере данных. Рассмотрите добавление памяти, использование TTL или пересмотр maxmemory_policy.","severity":"caution"},{"id":"dbsize-keyspace","group":"Ёмкость","title":"Число ключей и распределение keyspace","command":"redis-cli DBSIZE; redis-cli INFO keyspace","expect":"Отслеживайте общее число ключей (DBSIZE) и распределение по базам (INFO keyspace: keys, expires, avg_ttl). Растущий keyspace без TTL (мало expires) приводит к раздуванию памяти. Резкие скачки числа ключей могут быть признаком аномального поведения.","severity":"safe"},{"id":"hit-ratio","group":"Производительность","title":"Коэффициент попаданий в кэш (keyspace hits/misses)","command":"redis-cli INFO stats | grep -E \"keyspace_hits|keyspace_misses\"","expect":"Коэффициент попаданий = hits / (hits + misses). При использовании в качестве кэша он должен быть высоким (80%+). Низкий коэффициент указывает на неэффективное кэширование, неверные TTL или паттерн промахов; убедитесь, что часто запрашиваемые данные кэшируются.","severity":"safe"},{"id":"slowlog","group":"Производительность","title":"Журнал медленных команд (SLOWLOG)","command":"redis-cli SLOWLOG GET 10","expect":"Исследуйте медленные команды (порог по умолчанию 10ms). Команды O(N) (KEYS, SMEMBERS, большой HGETALL, SORT) блокируют однопоточный Redis и задерживают каждый запрос. Заменяйте их альтернативами вроде SCAN.","severity":"caution"},{"id":"clients","group":"Производительность","title":"Подключённые и заблокированные клиенты (clients)","command":"redis-cli INFO clients | grep -E \"connected_clients|blocked_clients|maxclients|client_recent_max_input_buffer\"","expect":"Если connected_clients приближается к maxclients, новые подключения отклоняются. Высокий blocked_clients (ожидание на BLPOP/BRPOP) может быть предусмотрен, но неожиданный рост — признак перегрузки. Проверьте утечки подключений.","severity":"safe"},{"id":"ops-per-sec","group":"Производительность","title":"Мгновенная пропускная способность (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 показывает текущую нагрузку. Резкие падения/всплески — признак аномалий. Если rejected_connections > 0, лимит maxclients достигнут. Так как Redis однопоточный, очень высокий ops может создать узкое место по CPU.","severity":"safe"},{"id":"rdb-last-save","group":"Бэкап и восстановление","title":"Последнее сохранение RDB и несохранённые изменения","command":"redis-cli INFO persistence | grep -E \"rdb_last_save_time|rdb_changes_since_last_save|rdb_last_bgsave_status\"; redis-cli LASTSAVE","expect":"Если rdb_changes_since_last_save очень велик, с последнего снимка накопилось много данных; при сбое эти изменения теряются. Если rdb_last_save_time (LASTSAVE, Unix-время) очень старый, пересмотрите интервал сохранения или частоту бэкапов.","severity":"caution"},{"id":"aof-rewrite","group":"Бэкап и восстановление","title":"Статус перезаписи AOF","command":"redis-cli INFO persistence | grep -E \"aof_enabled|aof_last_bgrewrite_status|aof_rewrite_in_progress|aof_current_size|aof_base_size\"","expect":"В сценариях, требующих надёжности, должен быть задан aof_enabled=1. aof_last_bgrewrite_status должен быть 'ok'; 'err' указывает на проблему с диском/сохранением. Если aof_current_size намного больше base_size, нужна перезапись.","severity":"caution"},{"id":"acl-auth","group":"Безопасность","title":"Настройка ACL / аутентификации (requirepass)","command":"redis-cli ACL LIST; redis-cli ACL GETUSER default","expect":"Должен быть хотя бы один пользователь, защищённый паролем. Если флаги в выводе ACL GETUSER default показывают 'on' и 'nopass' (requirepass — это тоже пароль этого пользователя), Redis открыт для доступа без аутентификации; это серьёзная брешь в безопасности. Пароли отображаются только как хэши; не используйте для этой проверки CONFIG GET requirepass, так как он выводит пароль открытым текстом. Определяйте пользователей с минимальными привилегиями через ACL.","severity":"danger"},{"id":"dangerous-commands","group":"Безопасность","title":"Опасные команды и 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 должен быть 'yes', а bind ограничен безопасными интерфейсами. Опасные команды вроде FLUSHALL, FLUSHDB, CONFIG, KEYS и DEBUG в продакшене следует отключить через rename-command или ограничить через ACL.","severity":"caution"},{"id":"replication-status","group":"Репликация","title":"Статус репликации (role, master link)","command":"redis-cli INFO replication","expect":"Проверьте role (master/slave), connected_slaves и статус каждой реплики. На реплике master_link_status должен быть 'up'; 'down' означает разрыв связи. Если master_last_io_seconds_ago велик (отставание), риск потери данных и несогласованности возрастает.","severity":"danger"}]},"health/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"checks":[{"id":"cluster-health","group":"Общее","title":"Здоровье кластера (green / yellow / red)","command":"GET _cluster/health","expect":"status должен быть 'green': все primary- и replica-шарды размещены. 'yellow' означает, что primary целы, но часть реплик не назначена (риск для избыточности); 'red' — потерян/не назначен хотя бы один primary-шард (часть данных недоступна, нужны немедленные действия). unassigned_shards должен быть 0.","severity":"danger"},{"id":"cluster-version","group":"Общее","title":"Версия и информация о кластере","command":"GET /","expect":"version.number должна быть поддерживаемой версией с установленными патчами безопасности. Старые/EOL (end-of-life) версии несут риски безопасности и стабильности. Убедитесь также, что все узлы работают на одной версии, через GET _cat/nodes?v&h=name,version (временное смешение во время rolling upgrade нормально).","severity":"safe"},{"id":"pending-tasks","group":"Общее","title":"Ожидающие задачи кластера","command":"GET _cluster/pending_tasks","expect":"Список должен быть пустым или очень коротким. Постоянно накапливающиеся и долго ожидающие задачи (высокий time_in_queue) указывают, что узел master перегружен или обновления состояния кластера застопорились; это ухудшает время отклика всего кластера.","severity":"caution"},{"id":"disk-allocation-watermark","group":"Ёмкость","title":"Размещение на дисках и состояние порогов watermark","command":"GET _cat/allocation?v","expect":"disk.percent каждого узла должен быть ниже high watermark (по умолчанию 90%). Выше 85% — предупреждение, 90% (high) запускает перемещение шардов, а 95% (flood_stage) переводит индексы в режим только для чтения. Убедитесь, что столбец shards сбалансирован по узлам; неравномерное распределение приводит к раннему заполнению одного узла.","severity":"danger"},{"id":"largest-indices","group":"Ёмкость","title":"Самые большие индексы (store size)","command":"GET _cat/indices?v&s=store.size:desc","expect":"Отслеживайте индексы, занимающие больше всего места, и их значения doc.count. Неконтролируемый рост индексов журналов/метрик — главная причина заполнения дисков; задайте для них политики rollover и удаления через ILM (Index Lifecycle Management). Особое внимание уделяйте индексам со статусом 'close' или со здоровьем yellow/red.","severity":"caution"},{"id":"jvm-heap-usage","group":"Ёмкость","title":"Использование JVM heap и RAM (по узлам)","command":"GET _cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m","expect":"heap.percent должен стабильно оставаться ниже 75%; устойчивые значения 85%+ означают давление на память, частые паузы GC и риск срабатывания circuit breaker. JVM heap должен составлять ~50% RAM сервера и не более ~31GB (предел compressed oops). При высоком heap, который не снижается, оптимизируйте запросы или добавьте узлы.","severity":"caution"},{"id":"shard-count-limits","group":"Ёмкость","title":"Число шардов и лимит шардов на узел","command":"GET _cluster/health?level=shards","expect":"active_shards и число шардов на узел должны быть разумными. Верхний предел по умолчанию — cluster.max_shards_per_node = 1000 (на data-узел); приближение к нему и тысячи мелких шардов в сумме (oversharding) создают давление на master и расходуют память впустую. Старое правило «~20 шардов на GB heap» не применяется к 8.3+; текущая рекомендация — 10-50GB на шард и планирование heap по числу полей (mappings) на узел. initializing_shards / relocating_shards не должны долго оставаться высокими. Если шардов слишком много, используйте более крупные/меньшее число шардов или rollover.","severity":"caution"},{"id":"http-connections","group":"Ёмкость","title":"Число HTTP-подключений","command":"GET _nodes/stats/http","expect":"http.current_open должен быть в разумных пределах. Если http.total_opened растёт очень быстро, клиенты не используют пул подключений и открывают новое подключение на каждый запрос (неэффективно). Аномально высокий current_open указывает на утечку подключений или возможное исчерпание ресурсов.","severity":"safe"},{"id":"thread-pool-rejections","group":"Производительность","title":"Отказы thread pool и переполнение очередей","command":"GET _cat/thread_pool?v&h=node_name,name,active,queue,rejected","expect":"Столбец rejected должен быть 0. Растущее значение rejected в пулах write/bulk и search показывает, что кластер перегружен и возвращает HTTP 429 (es_rejected_execution_exception). Постоянно заполненная очередь — признак перегрузки. Применяйте backoff на клиенте, уменьшите размер пакета или добавьте мощности.","severity":"danger"},{"id":"query-cache-hit","group":"Производительность","title":"Коэффициент попаданий query cache","command":"GET _nodes/stats/indices/query_cache","expect":"Отношение hit_count / (hit_count + miss_count) должно быть высоким для повторяющихся запросов с фильтрами. Низкий коэффициент попаданий при большом числе вытеснений указывает, что размер кэша недостаточен или запросы слишком изменчивы для кэширования. Использование стабильных фильтров (filter context) повышает коэффициент попаданий.","severity":"safe"},{"id":"slowlog-config","group":"Производительность","title":"Настройки журнала медленных запросов (slowlog)","command":"GET /_all/_settings?filter_path=**.search.slowlog,**.indexing.slowlog","expect":"На важных индексах должны быть заданы пороги slowlog для поиска и индексации (напр. index.search.slowlog.threshold.query.warn: 10s). При отключённом slowlog диагностика медленных запросов затрудняется. Включите пороги и отслеживайте журналы, чтобы выявлять дорогие запросы (широкие агрегации, wildcards, глубокий постраничный вывод).","severity":"safe"},{"id":"hot-threads","group":"Производительность","title":"Hot threads","command":"GET _nodes/hot_threads","expect":"Показывает потоки, потребляющие больше всего CPU. Используется при высокой загрузке CPU/медленной работе, чтобы определить, какая операция (тяжёлая агрегация, merge, GC, скрипт) является узким местом. Если один и тот же дорогой запрос/merge появляется постоянно, оптимизируйте этот запрос или стратегию индексации.","severity":"safe"},{"id":"fielddata-memory","group":"Производительность","title":"Использование памяти fielddata","command":"GET _cat/fielddata?v","expect":"Использование памяти fielddata нужно держать низким. Агрегация/сортировка по полям text загружает fielddata в память и повышает риск ошибок circuit breaker (Data too large) и OOM. Для полей с высоким fielddata переходите на подполе keyword (doc_values).","severity":"caution"},{"id":"snapshot-status","group":"Бэкап и восстановление","title":"Репозитории снимков и статус последнего снимка","command":"GET _snapshot/_all","expect":"Должен быть определён хотя бы один репозиторий снимков. Убедившись, что репозитории есть, проверьте статус последних снимков (вместе с проверкой verify ниже). Если репозитория нет, у кластера нет бэкапа, и в состоянии RED данные могут быть безвозвратно потеряны. Настройте SLM (Snapshot Lifecycle Management) для регулярных автоматических снимков.","severity":"danger"},{"id":"snapshot-latest-state","group":"Бэкап и восстановление","title":"Успешность последних снимков","command":"GET _snapshot/<repo>/_all?verbose=false","expect":"state последних снимков должен быть 'SUCCESS'. 'PARTIAL' означает, что часть шардов не удалось сохранить, 'FAILED' — что бэкап не удался; оба означают потерю данных при восстановлении. Дата последнего снимка (end_time) не должна быть слишком старой (делайте бэкапы с частотой, отвечающей вашей цели RPO).","severity":"danger"},{"id":"snapshot-repo-verify","group":"Бэкап и восстановление","title":"Проверка репозитория снимков","command":"POST _snapshot/<repo>/_verify","expect":"Проверка подтверждает, что все узлы имеют доступ к репозиторию (общая файловая система, S3, HDFS и т. д.), и перечисляет в ответе узлы с доступом. Если возвращается ошибка, часть узлов не может писать в репозиторий бэкапов; в этом случае снимки будут неполными/неудачными. Исправьте доступ к репозиторию и учётные данные.","severity":"caution"},{"id":"security-enabled","group":"Безопасность","title":"Включена ли безопасность (X-Pack security)","command":"GET _xpack/usage","expect":"security.enabled и security.available должны быть true. При отключённой безопасности кластер доступен без аутентификации; такой кластер, открытый в интернет или недоверенную сеть, — серьёзная уязвимость (утечка/удаление данных). Аутентификация, ролевой доступ и TLS должны быть включены.","severity":"danger"},{"id":"users-roles","group":"Безопасность","title":"Пользователи и роли","command":"GET _security/user","expect":"Просмотрите определённых пользователей и назначения ролей. Убедитесь, что встроенные учётные записи (elastic, kibana_system и т. д.) защищены надёжными паролями. Следуя принципу минимальных привилегий, определите для каждого приложения отдельных пользователей с ограниченными ролями; избегайте необоснованно широкого использования роли superuser. Для ролей используйте GET _security/role.","severity":"caution"},{"id":"tls-transport-http","group":"Безопасность","title":"Статус TLS / HTTPS (transport и HTTP)","command":"GET _nodes/settings?filter_path=**.xpack.security.transport.ssl.enabled,**.xpack.security.http.ssl.enabled","expect":"xpack.security.transport.ssl.enabled должен быть true (шифрование между узлами, обязательно). В идеале http.ssl.enabled тоже должен быть true (HTTPS от клиента к кластеру). При отключённом TLS учётные данные и данные передаются по сети открытым текстом. Проверьте также срок действия сертификатов и режим проверки (verification_mode: certificate/full).","severity":"danger"},{"id":"unassigned-shards","group":"Репликация","title":"Неназначенные шарды и их причины","command":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason","expect":"У всех шардов state должен быть 'STARTED'. Шарды в состоянии UNASSIGNED и их unassigned.reason (NODE_LEFT, ALLOCATION_FAILED, DISK_THRESHOLD и т. д.) нужно расследовать. Неназначенный primary означает здоровье RED, неназначенная реплика — YELLOW. Для поиска первопричины используйте GET _cluster/allocation/explain.","severity":"danger"},{"id":"recovery-active","group":"Репликация","title":"Текущие восстановления шардов","command":"GET _cat/recovery?v&active_only=true","expect":"В норме активных восстановлений быть не должно (пустой список). Временное восстановление после добавления/удаления узла или отработки отказа нормально, но восстановления, долго застрявшие без прогресса (%progress не растёт), указывают на узкое место сети/диска. Если ребалансировка (relocation) влияет на нагрузку, пересмотрите настройки ограничения скорости восстановления (indices.recovery.max_bytes_per_sec).","severity":"caution"},{"id":"replica-count","group":"Репликация","title":"Число реплик по индексам","command":"GET _cat/indices?v&h=index,health,pri,rep,docs.count","expect":"В продуктивных индексах rep (number_of_replicas) должен быть не меньше 1; на индексе с 0 репликами потеря узла означает безвозвратную потерю данных. Однако в многоузловом кластере число реплик не должно превышать (число data-узлов - 1); иначе лишние реплики не могут быть размещены и здоровье остаётся YELLOW. Расследуйте индексы, у которых столбец health не green.","severity":"danger"}]},"recipes/oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"recipes":[{"id":"redo-switch","task":"Переключение redo log (сменить текущий файл журнала)","category":"Redo и архив","command":"ALTER SYSTEM SWITCH LOGFILE;","what":"Закрывает текущую группу redo log и переходит к следующей; в режиме archivelog запускает архивирование.","when":"Чтобы вручную создать архив, принудительно переключить журнал или для тестирования.","caution":"Частые переключения снижают производительность; если файлы журнала слишком малы, пересмотрите их размер.","tags":["redo","switch","logfile","archive","archive","alter system"]},{"id":"redo-add-group","task":"Добавить новую группу redo log","category":"Redo и архив","command":"ALTER DATABASE ADD LOGFILE GROUP 4\n  ('/u01/oradata/ORCL/redo04a.log',\n   '/u02/oradata/ORCL/redo04b.log') SIZE 512M;","what":"Создаёт новую группу redo log с заданным размером и членами (несколько членов = зеркало).","when":"Когда пропускной способности redo недостаточно или нужно увеличить число групп.","caution":"Убедитесь, что номер группы не пересекается с существующими группами; проверьте через V$LOG.","tags":["redo","logfile","group","add","add","alter database"]},{"id":"redo-resize","task":"Изменить размер группы redo log (drop + add)","category":"Redo и архив","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":"Размер redo log нельзя изменить напрямую; это делается удалением группы и повторным добавлением с новым размером.","when":"Когда файлы redo слишком малы/велики, для настройки частоты контрольных точек.","caution":"Удалить можно только группу INACTIVE или UNUSED; не удаляйте группу CURRENT/ACTIVE. Всегда должно оставаться минимум две группы. REUSE повторно использует существующий файл.","tags":["redo","resize","size","drop","add","logfile","reuse"]},{"id":"enable-archivelog","task":"Перевести базу в режим ARCHIVELOG","category":"Redo и архив","command":"SHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;\n-- Verify:\nARCHIVE LOG LIST;","what":"Переводит базу в режим архивирования журналов; заполненные redo log архивируются, что делает возможными горячие бэкапы и восстановление на момент времени.","when":"Для стратегии бэкапа и восстановления продуктивной базы (обязательно перед RMAN, Data Guard).","caution":"Требует перезапуска экземпляра; запланируйте простой. Убедитесь, что в месте назначения архивов (FRA или LOG_ARCHIVE_DEST) достаточно места; если оно заполнится, БД остановится. В RAC: остановите все экземпляры через srvctl stop database -d DB, откройте один экземпляр через STARTUP MOUNT, переключите в ARCHIVELOG, остановите его и запустите через srvctl start database -d DB (в 11gR2+ cluster_database=false не требуется).","tags":["archivelog","archive","mode","mount","recovery","recovery"]},{"id":"fra-full-fix","task":"Исправить заполненную FRA / место назначения архивов","category":"Redo и архив","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":"Когда Fast Recovery Area или место назначения архивов заполняются, база зависает (ORA-00257). Исправляется увеличением пространства или очисткой архивов.","when":"Ошибка archiver ORA-00257, когда БД перестаёт принимать новые транзакции.","caution":"Удаление архивов, для которых нет бэкапа, уничтожает возможность восстановления. Сначала сделайте бэкап RMAN, затем удаляйте. Не удаляйте через rm из ОС; это потребует RMAN CROSSCHECK. Пока FRA заполнена, сохраняйте архивы в место вне FRA (FORMAT '/backup/arc_%U'). С Data Guard не удаляйте архивы, ещё не переданные/не применённые на standby; иначе на standby образуется разрыв (gap). Используйте CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY.","tags":["fra","recovery area","ora-00257","archiver","archive","full","cleanup"]},{"id":"redo-status","task":"Посмотреть статус redo log","category":"Redo и архив","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":"Показывает статус групп redo log (CURRENT/ACTIVE/INACTIVE), их размеры, файлы-члены и статус архивирования.","when":"При просмотре конфигурации redo или чтобы увидеть активную группу перед изменением размера.","caution":"Только запрос; риска нет.","tags":["redo","status","status","v$log","v$logfile","logfile"]},{"id":"asm-add-disk","task":"Добавить диски в дисковую группу ASM","category":"ASM","command":"-- Connect to the ASM instance (sqlplus / as sysasm)\nALTER DISKGROUP DATA ADD DISK\n  '/dev/oracleasm/disks/DATA05' NAME DATA_0005,\n  '/dev/oracleasm/disks/DATA06' NAME DATA_0006\n  REBALANCE POWER 4;","what":"Добавляет новые диски в дисковую группу DATA и запускает автоматический rebalance; ёмкость растёт.","when":"Когда дисковая группа ASM начинает заполняться или при расширении хранилища.","caution":"Требуется привилегия SYSASM. Размеры дисков должны быть близкими. Rebalance создаёт нагрузку I/O; в часы пик используйте низкий POWER.","tags":["asm","diskgroup","add disk","add disk","rebalance","alter diskgroup"]},{"id":"asm-drop-disk","task":"Удалить диск из дисковой группы ASM","category":"ASM","command":"ALTER DISKGROUP DATA DROP DISK DATA_0005 REBALANCE POWER 4;\n\n-- WAIT until the drop completes (the disk can be physically removed once V$ASM_OPERATION is empty):\nSELECT group_number, operation, state, power,\n       sofar, est_work, est_minutes\nFROM   v$asm_operation;","what":"Удаляет диск из дисковой группы; его экстенты перемещаются на оставшиеся диски (rebalance). По завершении HDR_STATUS диска становится FORMER.","when":"При замене отказавшего диска, сокращении ёмкости или миграции хранилища.","caution":"НЕ извлекайте диск физически ДО завершения rebalance, иначе данные будут потеряны. Убедитесь, что на оставшихся дисках достаточно свободного места.","tags":["asm","drop disk","remove disk","rebalance","v$asm_operation","alter diskgroup"]},{"id":"asm-rebalance-power","task":"Задать POWER для rebalance ASM","category":"ASM","command":"-- Change the power of a running rebalance (1-1024, 0 stops it):\nALTER DISKGROUP DATA REBALANCE POWER 8;\n\n-- Stop the rebalance completely:\nALTER DISKGROUP DATA REBALANCE POWER 0;","what":"Задаёт параллелизм/скорость текущего rebalance. Более высокий POWER — быстрее, но больше I/O.","when":"Чтобы ускорить слишком медленный rebalance или замедлить/остановить тот, что влияет на продакшен.","caution":"POWER 0 приостанавливает rebalance (удаление диска не завершится). Высокое значение влияет на I/O продакшена.","tags":["asm","rebalance","power","power","diskgroup","tuning"]},{"id":"asm-diskgroup-space","task":"Свободное место в дисковых группах ASM","category":"ASM","command":"SELECT name,\n       state,\n       type,\n       total_mb/1024                 AS total_gb,\n       free_mb/1024                  AS free_gb,\n       ROUND((1-free_mb/total_mb)*100,1) AS pct_used,\n       usable_file_mb/1024           AS usable_gb\nFROM   v$asm_diskgroup\nORDER BY name;","what":"Показывает общее, свободное и пригодное к использованию (с учётом избыточности) пространство каждой дисковой группы.","when":"Для планирования ёмкости, при нехватке места, перед решением о добавлении дисков.","caution":"usable_file_mb учитывает избыточность; при избыточности NORMAL/HIGH этот столбец — реально доступное пространство. Только запрос; риска нет.","tags":["asm","diskgroup","space","space","free","v$asm_diskgroup","capacity"]},{"id":"asm-monitor-rebalance","task":"Мониторинг rebalance ASM","category":"ASM","command":"SELECT group_number, operation, pass, state, power,\n       sofar, est_work,\n       ROUND(sofar/NULLIF(est_work,0)*100,1) AS pct_done,\n       est_minutes\nFROM   v$asm_operation\nORDER BY group_number;","what":"Показывает прогресс, power и оценку оставшегося времени текущих операций rebalance/resync.","when":"Чтобы отследить, завершился ли rebalance после добавления/удаления дисков.","caution":"Пустой результат = активных операций нет (завершено). Только запрос; риска нет.","tags":["asm","rebalance","monitor","monitor","v$asm_operation","progress"]},{"id":"add-tablespace","task":"Добавить новое табличное пространство","category":"Хранение","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":"Создаёт новое постоянное табличное пространство для размещения сегментов таблиц/индексов.","when":"При выделении отдельного хранилища для нового приложения/схемы.","caution":"Укажите MAXSIZE, иначе диск может заполниться. В ASM имя файла генерируется автоматически.","tags":["tablespace","create","create","datafile","storage","storage","autoextend"]},{"id":"add-datafile","task":"Добавить файл данных в существующее табличное пространство","category":"Хранение","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":"Увеличивает ёмкость существующего табличного пространства добавлением нового файла данных.","when":"Когда табличное пространство заполнено и отдельный файл приближается к пределу smallfile (32G/64G).","caution":"У smallfile-табличного пространства есть предел числа блоков на файл; при необходимости добавьте новый файл. Укажите MAXSIZE.","tags":["datafile","add","add","tablespace","alter tablespace","storage"]},{"id":"resize-datafile","task":"Изменить размер файла данных / настроить autoextend","category":"Хранение","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":"Задаёт фиксированный размер файла данных или настраивает его автоматический рост.","when":"Чтобы вернуть место (сжать), заранее выделить место или остановить неконтролируемый рост.","caution":"Сжатие через RESIZE не может опуститься ниже занятых блоков в конце файла (ORA-03297). Сначала переместите сегменты.","tags":["datafile","resize","size","autoextend","maxsize","alter database"]},{"id":"move-datafile-online","task":"Переместить файл данных онлайн","category":"Хранение","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":"Перемещает файл данных в новое расположение при открытой базе (12c+); меняет хранилище без простоя.","when":"При смене диска/точки монтирования, миграции в ASM или переносе на быстрое хранилище.","caution":"Требуются 12c или новее и Enterprise Edition. Перемещение создаёт дополнительный I/O. Если не использовать KEEP, старый файл удаляется.","since":"Oracle 12c","note":"Онлайн-перемещение файла данных (ALTER DATABASE MOVE DATAFILE) появилось в Oracle 12c (12.1); до этого перемещение файла данных означало перевод табличного пространства/файла в OFFLINE, копирование на уровне ОС и переназначение через ALTER DATABASE RENAME FILE.","tags":["datafile","move","move","online","alter database move datafile","asm"]},{"id":"temp-tablespace","task":"Добавить временное табличное пространство / сменить по умолчанию","category":"Хранение","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":"Даёт временное пространство для операций sort/hash/глобальных временных таблиц; можно назначить новое табличное пространство по умолчанию.","when":"При ORA-01652 (unable to extend temp segment), узком месте temp или изменении размера temp.","caution":"После смены значения по умолчанию не выполняйте сразу DROP старого temp, если он используется; удаляйте его, когда сессии освободят его.","tags":["temp","tablespace","tempfile","ora-01652","default temporary","storage"]},{"id":"tablespace-space","task":"Запрос свободного места в табличных пространствах","category":"Хранение","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":"Показывает общее и свободное пространство и процент заполнения каждого табличного пространства (по выделенному сейчас пространству, без учёта потенциала autoextend).","when":"Мониторинг пространства, поиск табличного пространства, которое нужно расширить.","caution":"Этот запрос основан на текущем размере файлов данных; с autoextend они могут вырасти ещё. Для temp используйте DBA_TEMP_FREE_SPACE. Только запрос; риска нет.","tags":["tablespace","space","space","free","dba_free_space","usage","storage"]},{"id":"alter-parameter","task":"Изменить параметр (со SCOPE)","category":"Память и параметры","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":"Меняет init-параметр в работающем экземпляре и/или в spfile. SCOPE определяет поведение.","when":"Для настройки, корректировки ёмкости и исправления ошибок, требующих изменения параметра.","caution":"Статические параметры (processes, sga_max_size...) принимают только SCOPE=SPFILE и требуют перезапуска. Неверное значение может сделать запуск экземпляра невозможным; для критичных параметров сначала сделайте резервную копию pfile.","tags":["parameter","parameter","alter system","scope","spfile","tuning"]},{"id":"flush-pool","task":"Очистка shared pool / buffer cache (flush)","category":"Память и параметры","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":"Очистка shared pool вытесняет из памяти все кэшированные SQL/PL-SQL и информацию словаря; очистка buffer cache вытесняет все блоки данных.","when":"Для проверки проблем с планами, после разовой большой операции или для согласованного старта бенчмарка.","caution":"Осторожно В ПРОДАКШЕНЕ! После очистки все SQL разбираются заново (шторм hard parse), а блоки читаются с диска; следует серьёзное временное падение производительности.","tags":["flush","shared pool","buffer cache","memory","alter system","cache"]},{"id":"spfile-pfile","task":"Создать pfile из spfile и наоборот","category":"Память и параметры","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":"Преобразует между бинарным spfile и текстовым pfile; pfile можно редактировать вручную.","when":"Резервная копия перед изменением параметров, восстановление повреждённого spfile, ручное исправление статического параметра.","caution":"После CREATE SPFILE FROM PFILE изменения вступают в силу только после перезапуска. Проверьте путь перед перезаписью существующего spfile.","tags":["spfile","pfile","create pfile","create spfile","parameter","backup"]},{"id":"sga-pga-target","task":"Изменить целевые размеры SGA / PGA","category":"Память и параметры","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":"Задаёт целевые объёмы памяти SGA и PGA; Oracle управляет компонентами автоматически (ASMM/AMM).","when":"При добавлении RAM на сервер, при узком месте или давлении на память.","caution":"sga_target не может превышать sga_max_size. sga_max_size статический и требует перезапуска. Оставляйте RAM для операционной системы и других экземпляров; избыточное выделение вызывает swapping/OOM.","tags":["sga","pga","memory","memory","target","sga_target","pga_aggregate_target"]},{"id":"kill-session","task":"Завершить сессию (kill session)","category":"Сессии и процессы","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":"Завершает сессию с заданными SID,SERIAL#; её открытая транзакция откатывается.","when":"Чтобы остановить сессию, удерживающую блокировки, выполняющую неконтролируемый запрос или с утечкой подключений.","caution":"При открытой транзакции откат может занять много времени (быстро завершается, если работы было мало). Сначала проверьте запросом, чтобы не завершить не тот SID. Откат большого DML создаёт нагрузку I/O.","tags":["kill session","kill session","v$session","alter system","sid","serial"]},{"id":"long-running-queries","task":"Долгие запросы / сессии","category":"Сессии и процессы","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":"Выводит активные сессии, работающие дольше 60 секунд, и прогресс долгих пакетных операций.","when":"При жалобах на медленную работу — чтобы найти запрос, загружающий систему.","caution":"last_call_et — время с последнего вызова; оно может быть большим и у простаивающей сессии, поэтому важен фильтр status='ACTIVE'. Только запрос; риска нет.","tags":["long running","long query","v$session","v$sql","v$session_longops","active"]},{"id":"find-blocking","task":"Найти блокирующую сессию","category":"Сессии и процессы","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":"Выводит пары блокирующих и ожидающих сессий со временем ожидания и событием.","when":"Чтобы найти виновника при проблемах с блокировками вроде «приложение зависло / ожидание enq: TX».","caution":"Перед завершением блокирующей сессии согласуйте с владельцем бизнес-процесса; это может быть критичная операция. Только запрос; риска нет.","tags":["blocking","lock","lock","blocker","waiter","v$session","blocking_session"]},{"id":"gather-stats","task":"Сбор статистики (DBMS_STATS)","category":"Обслуживание","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":"Собирает статистику таблиц/индексов для оптимизатора; обеспечивает построение правильных планов выполнения.","when":"После крупных DML/загрузок, при деградации планов, после создания нового индекса/таблицы.","caution":"Изменение статистики может изменить планы выполнения; на критичных системах сначала протестируйте/сравните с AWR. Параллелизм DEGREE потребляет CPU. GATHER_DATABASE_STATS — тяжёлая операция в часы пиковой нагрузки.","tags":["dbms_stats","statistics","statistics","gather","optimizer","maintenance"]},{"id":"rebuild-index-online","task":"Перестроить индекс онлайн","category":"Обслуживание","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":"Перестраивает индекс без блокировки DML (ONLINE); устраняет фрагментацию и позволяет сменить табличное пространство.","when":"Раздувание/фрагментация индекса, перенос индекса в другое табличное пространство, восстановление индекса в состоянии UNUSABLE.","caution":"ONLINE требует дополнительного временного пространства и I/O. PARALLEL сохраняется; после перестройки сбросьте его через NOPARALLEL, иначе запросы будут непреднамеренно выполняться параллельно. ONLINE поддерживается для bitmap-индексов не во всех версиях. Перестройка ONLINE доступна только в Enterprise Edition.","tags":["index","rebuild","online","rebuild","alter index","fragmentation","maintenance"]},{"id":"rman-full-backup","task":"Быстрый полный бэкап через RMAN","category":"Обслуживание","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":"Создаёт сжатый полный бэкап всей базы данных и архивных журналов параллельно по двум каналам, делает бэкап controlfile и удаляет устаревшие бэкапы.","when":"Ежедневный/еженедельный полный бэкап, создание точки восстановления.","caution":"COMPRESSED потребляет CPU. DELETE OBSOLETE удаляет старые бэкапы согласно политике хранения; настройте политику (CONFIGURE RETENTION POLICY) правильно. ARCHIVELOG требует, чтобы БД работала в режиме ARCHIVELOG. Убедитесь, что в месте назначения бэкапа достаточно места.","tags":["rman","backup","backup","full","compressed","archivelog","maintenance"]},{"id":"controlfile-trace","task":"Сделать trace-бэкап controlfile","category":"Обслуживание","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":"Генерирует SQL-скрипт (trace), пересоздающий controlfile, и/или его двоичную копию.","when":"После структурных изменений (добавление datafile/redo), при подготовке к аварийному восстановлению или для пересоздания controlfile при потере всех копий.","caution":"Обновляйте trace-файл при каждом изменении структуры; старый скрипт создаст неверную структуру. REUSE перезаписывает существующий файл. Риска для системы нет, но храните созданный файл в защищённом месте.","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":"Проверить состояние базы данных, экземпляров и сервисов RAC через 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":"Показывает текущее состояние базы данных, каждого экземпляра и его сервисов с точки зрения Clusterware (CRS), а также зарегистрированную конфигурацию; -verbose добавляет режим открытия экземпляра.","when":"Как первая проверка до и после любого вмешательства в RAC; во время сбоя — чтобы увидеть, какой узел/экземпляр затронут.","caution":"Только чтение. Запускайте из Oracle home базы данных (пользователь oracle); srvctl из Grid home другой версии может выдавать вводящие в заблуждение результаты или ошибки.","since":"12c","note":"Длинные опции -db/-instance/-service доступны с 12c+; короткие опции 11g (-d, -i, -s) в 19c ещё работают, но объявлены устаревшими.","tags":["rac","srvctl","status","instance","service","cluster","config"],"untested":true},{"id":"rac-srvctl-start-stop","task":"Остановить / запустить базу данных RAC или отдельный экземпляр через 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":"Контролируемо останавливает/запускает базу данных или выбранный экземпляр через Clusterware; с -failover сервисы остановленного экземпляра переносятся на доступные экземпляры, а -drain_timeout ожидает завершения сессий.","when":"Для поочерёдных (узел за узлом) перезапусков при патчинге, обслуживании ОС или изменении параметров; для планового полного останова.","caution":"В RAC используйте srvctl вместо SHUTDOWN в SQL*Plus; иначе Clusterware не знает TARGET ресурса и может перезапустить экземпляр. stop database останавливает все экземпляры = полный простой. Если для сервисов не определены available-экземпляры, -failover не сможет перенести подключения.","since":"12c","note":"-drain_timeout и -failover доступны с 12.2+. Используйте -stopoption abort только в экстренных случаях; при следующем запуске потребуется instance recovery.","tags":["rac","srvctl","stop","start","rolling","drain","shutdown"],"untested":true},{"id":"rac-service-add","task":"Добавить сервис RAC с preferred/available-экземплярами (включая 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":"Определяет сервис для приложений, который обычно работает на preferred-экземплярах и при их отказе переходит на available-экземпляр; с -role PRIMARY он запускается только в роли primary в Data Guard.","when":"Чтобы приложения подключались к сервису, а не к экземпляру, для распределения нагрузки по узлам и для прозрачного переключения (drain/failover) при плановом обслуживании.","caution":"Приложения должны подключаться по этому имени сервиса (через SCAN), а не по SID или имени экземпляра. -failovertype TRANSACTION (Application Continuity) требует поддержки со стороны приложения/драйвера; если не уверены, начните с -failovertype SELECT (TAF) или NONE. Не используйте для приложений сервисы SYS$ и сервис по умолчанию, названный по db_unique_name.","since":"12c","note":"-pdb доступен с 12c+, -drain_timeout и -stopoption — с 12.2+. Изменение выполняется через srvctl modify service; постоянная смена preferred требует -modifyconfig.","tags":["rac","service","srvctl","preferred","available","taf","application continuity"],"untested":true},{"id":"rac-service-relocate","task":"Перенести сервис RAC на другой экземпляр","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":"Переносит работающий сервис с одного экземпляра/узла на другой; новые подключения идут на целевой экземпляр, а существующие сессии закрываются сами в течение drain_timeout.","when":"Чтобы освободить узел перед обслуживанием, вернуть сервис на preferred-экземпляр после failover или перебалансировать нагрузку.","caution":"Перенос временный: он не меняет конфигурацию сервиса (preferred/available); при перезапуске экземпляра сервис возвращается на preferred-экземпляр. -force завершает существующие сессии; если приложение не умеет переподключаться, пользователи увидят простой.","since":"12c","note":"-drain_timeout и -wait доступны с 12.2+. SQL-запрос работает и на одиночном экземпляре; часть со srvctl требует RAC.","tags":["rac","service","relocate","move","srvctl","drain","gv$session"],"untested":true},{"id":"rac-crsctl-stat-res","task":"Проверить состояние ресурсов кластера (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":"Выводит в табличном виде все ресурсы под управлением Clusterware (база данных, экземпляр, listener, SCAN, VIP, ASM, дисковая группа, сеть) по узлам с TARGET/STATE.","when":"После запуска узла, после патчинга и при любом расследовании сбоя; для поиска ресурсов в состоянии OFFLINE, INTERMEDIATE или UNKNOWN.","caution":"Только чтение. Не управляйте ресурсами ora.* напрямую через crsctl start/stop/modify; для ресурсов базы данных используйте srvctl (поддерживаемый способ).","note":"Имя ресурса содержит db_unique_name в нижнем регистре (ora.orcl.db). Вывод -init полностью виден только под root/grid.","tags":["rac","grid","crsctl","resource","resource","clusterware","status"],"untested":true},{"id":"rac-crsctl-check-cluster","task":"Проверить работоспособность стека кластера, список узлов и версию Clusterware","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":"Показывает, что демоны Cluster Ready Services, Cluster Synchronization Services и Event Manager работают на каждом узле, членов кластера, а также активную/программную версию Clusterware.","when":"При утренней проверке работоспособности, после вытеснения (eviction) или перезагрузки узла, после rolling-патча/обновления — чтобы убедиться, что все узлы на одном уровне.","caution":"Только чтение. Если activeversion -f показывает \"ROLLING PATCH\" или другой уровень патча, rolling-операция не завершена; завершите её (crsctl stop rollingpatch / rootcrs -postpatch), прежде чем вносить новые изменения.","since":"12c","note":"activeversion -f и softwareversion -all доступны с 12c+.","tags":["rac","grid","crsctl","olsnodes","health","cluster","version"],"untested":true},{"id":"rac-ocr-backup","task":"Проверить целостность OCR и сделать бэкап OCR (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":"Показывает целостность, расположение и бэкапы Oracle Cluster Registry (хранилища конфигурации кластера); -manualbackup немедленно создаёт физический бэкап OCR.","when":"Непосредственно перед изменениями конфигурации кластера: добавлением/удалением узлов, патчингом/обновлением Grid, изменением voting disk или сети (oifcfg).","caution":"ocrconfig -restore должен выполняться при остановленном CRS на всех узлах и в эксклюзивном режиме; восстановление не того бэкапа может привести к тому, что кластер не запустится, поэтому выполняйте его вместе с Oracle Support. Автоматические бэкапы по умолчанию могут храниться на локальном диске одного узла; перенесите их в общую дисковую группу.","note":"При запуске от пользователя grid ocrcheck пропускает проверку логического повреждения (\"Logical corruption check bypassed\"). Бэкапы в дисковую группу доступны с 12c+.","tags":["rac","ocr","ocrcheck","ocrconfig","backup","backup","grid"],"untested":true},{"id":"rac-votedisk-query","task":"Проверить voting disk и тайм-ауты CSS","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":"Показывает состояние voting disk (voting files), используемых для членства в кластере, и тайм-ауты сетевого/дискового heartbeat; replace votedisk переносит все voting files в целевую дисковую группу.","when":"При изменениях хранилища, перепроектировании дисковых групп или расследовании вытеснения узла; чтобы убедиться, что большинство (кворум) voting disk доступно.","caution":"Узел, не имеющий доступа к более чем половине voting disk, перезагружает сам себя. В целевой дисковой группе должно быть достаточно failure group (normal redundancy: 3, high: 5; при необходимости используйте quorum failgroup). Не меняйте misscount/disktimeout без одобрения Oracle Support.","note":"Значения по умолчанию: misscount 30 s, disktimeout 200 s (Linux).","tags":["rac","voting disk","votedisk","css","misscount","quorum","eviction"],"untested":true},{"id":"rac-cluvfy-checks","task":"Выполнить пред- и постпроверки с помощью 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":"Проверяет сетевую связность, синхронизацию времени, OCR, ASM, эквивалентность пользователей и предварительные требования ОС на всех узлах; сообщает о проблемах с предлагаемыми исправлениями.","when":"До и после установки Grid, добавления узла и обновлений; как базовая проверка при необъяснимых проблемах кластера.","caution":"Только чтение, но подключается ко всем узлам по ssh; пользователю grid нужна ssh-эквивалентность. Не запускайте скрипты -fixup (root) без их просмотра; они меняют параметры ОС.","note":"Для более широкой проверки best practice используйте orachk/exachk из AHF. Последняя версия cluvfy с My Oracle Support даёт больше проверок.","tags":["rac","cluvfy","check","verification","precheck","grid","orachk"],"untested":true},{"id":"rac-node-eviction-diag","task":"Диагностировать причину вытеснения узла / неожиданной перезагрузки узла","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":"Последовательно проходит цепочку вытеснения узла: сообщения CRS-16xx в alert log CRS, потери heartbeat в ocssd.trc, метрики ОС из CHM и журналы всех узлов, собранные через TFA.","when":"Когда узел неожиданно перезагружается или вытесняется из кластера — для анализа первопричины (RCA) и SR в Oracle Support.","caution":"Собирайте журналы со всех узлов и за период события; trace-файлы ротируются, и доказательства могут быть утеряны. Проверьте, что часовые пояса на узлах совпадают (CTSS/NTP). Не применяйте \"исправления\" вроде увеличения misscount до сбора доказательств.","note":"Типичные причины: сбой/потеря пакетов в private interconnect, задержки I/O на voting disk, исчерпание CPU/памяти (OOM, swap), скачки времени. Путь указан для структуры ADR 12c+; в 11g он находится в $GRID_HOME/log/<host>/.","tags":["rac","eviction","reboot","ocssd","tfactl","oclumon","rca"],"untested":true},{"id":"rac-gv-cluster-waits","task":"Анализ ожиданий уровня кластера (gc waits) по экземплярам через 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":"С помощью представлений GV$ одним запросом показывает ожидания класса Cluster (gc cr/current block 2-way/3-way, gc buffer busy, gc cr request) и их среднюю задержку по всем экземплярам.","when":"Когда пользователи жалуются на медленную работу RAC — чтобы понять, связана ли проблема с interconnect/global cache и на каком экземпляре она сосредоточена.","caution":"gv$active_session_history требует лицензии Diagnostics Pack. Если avg_ms выше нескольких ms, сначала проверьте interconnect (MTU, потери пакетов, netstat -s) и CPU процессов LMS; gc buffer busy обычно указывает на горячий блок приложения (sequence, правосторонний индекс).","note":"Работает и на одиночном экземпляре (возвращает inst_id = 1).","tags":["rac","gv$","gc","wait","wait event","interconnect","global cache"]},{"id":"asm-asmcmd-lsdg-du","task":"Показать использование дисковых групп, дисков и каталогов через 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":"Показывает из командной строки redundancy, общее/свободное пространство и Usable_file_MB дисковых групп, состояние дисков и пространство, занимаемое базой данных в ASM.","when":"Для проверки ёмкости, выявления дисков-кандидатов перед добавлением дисков и поиска базы, потребляющей место, при срабатывании алерта \"disk group full\".","caution":"Только чтение. Если Usable_file_MB в lsdg отрицательный, дисковая группа не сможет восстановить redundancy после потери диска/failgroup; немедленно освободите место или добавьте диски. du возвращает два значения: Used_MB и Mirror_used_MB (использование с учётом зеркалирования).","note":"SQL-аналог см. в рецептах asm-diskgroup-space и asm-disk-health.","tags":["asm","asmcmd","lsdg","lsdsk","du","space","disk group"],"untested":true},{"id":"asm-disk-health","task":"Проверить состояние дисков ASM, счётчики ошибок и диски-кандидаты через 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":"Выводит для каждого диска header_status (MEMBER, CANDIDATE, PROVISIONED, FORMER), состояние mount/mode, failgroup и счётчики ошибок чтения/записи; число offline-дисков по дисковым группам и клиентов ASM.","when":"Перед добавлением дисков — чтобы убедиться, что новый LUN виден как CANDIDATE/PROVISIONED; при отказе диска — чтобы увидеть, какой диск OFFLINE, и наличие ошибок I/O.","caution":"Только чтение. v$asm_disk при каждом обращении выполняет обнаружение дисков и работает медленно на системах с большим числом дисков; для частого мониторинга используйте v$asm_disk_stat (без обнаружения, новые диски не показывает). Никогда не добавляйте диск с header_status MEMBER в другую дисковую группу с FORCE; это уничтожит существующие данные.","note":"Без ASM строк не возвращает; осмысленный вывод требует экземпляра ASM.","tags":["asm","v$asm_disk","disk","header_status","error","candidate","failgroup"]},{"id":"asm-diskgroup-attributes","task":"Просмотреть и изменить атрибуты дисковой группы ASM (compatible, disk_repair_time)","category":"ASM","command":"-- Current attributes:\nSELECT dg.name AS diskgroup, a.name, a.value\nFROM   v$asm_attribute a\nJOIN   v$asm_diskgroup dg ON dg.group_number = a.group_number\nWHERE  a.name IN ('compatible.asm','compatible.rdbms','au_size','disk_repair_time',\n                  'failgroup_repair_time','content.type','sector_size')\nORDER  BY dg.name, a.name;\n\n-- Change (ASM instance, as SYSASM):\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'disk_repair_time' = '8h';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.asm'   = '19.0.0.0.0';\nALTER DISKGROUP <diskgroup> SET ATTRIBUTE 'compatible.rdbms' = '19.0.0.0.0';\n\n-- asmcmd equivalent:\n-- asmcmd lsattr -l -G <diskgroup>\n-- asmcmd setattr -G <diskgroup> disk_repair_time '8h'","what":"Показывает и изменяет уровни совместимости дисковой группы (compatible.asm/rdbms), размер AU и время, в течение которого временно отказавший диск сохраняется до удаления (disk_repair_time).","when":"После обновления Grid — для включения новых возможностей ASM (flex disk groups, увеличенные лимиты файлов) или для увеличения disk_repair_time при плановом обслуживании хранилища.","caution":"compatible.asm и compatible.rdbms НЕЛЬЗЯ ПОНИЗИТЬ ПОСЛЕ ПОВЫШЕНИЯ. Если compatible.rdbms выше параметра COMPATIBLE самой старой базы, использующей эту дисковую группу, эта база не сможет её смонтировать. Сначала повышайте compatible.asm, затем compatible.rdbms. au_size задаётся только при создании дисковой группы.","note":"Без ASM запрос не возвращает строк. disk_repair_time по умолчанию — 12h.","tags":["asm","attribute","compatible","disk_repair_time","disk group","au_size","v$asm_attribute"],"untested":true},{"id":"dg-show-configuration","task":"Показать конфигурацию брокера Data Guard и состояние баз данных (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":"Показывает общий статус конфигурации брокера (SUCCESS/WARNING/ERROR), режим защиты, роль каждой базы, задержку transport/apply и несоответствия между брокером и spfile.","when":"До и после каждой операции Data Guard (switchover, failover, изменение параметров); при ежедневной проверке работоспособности.","caution":"Только чтение. Пока брокер включён, не меняйте параметры Data Guard, такие как log_archive_dest_n или log_archive_config, через SQL; брокер перезапишет их своими значениями. Вносите изменения через EDIT DATABASE ... SET PROPERTY, а в 19c+ используйте EDIT DATABASE ... SET PARAMETER для параметров инициализации.","note":"StatusReport выводит ошибки ORA- по каждой базе; это самый быстрый способ найти причину статуса WARNING. InconsistentProperties объявлен устаревшим в 19c и всегда возвращает пустое значение; для несоответствий redo transport используйте InconsistentLogXptProps.","tags":["data guard","dgmgrl","broker","configuration","standby","status","lag"],"untested":true},{"id":"dg-readiness-sql","task":"Проверить роль primary/standby и готовность к switchover через 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":"Показывает роль базы данных, режим открытия, режим защиты, switchover_status (TO STANDBY / TO PRIMARY / SESSIONS ACTIVE / NOT ALLOWED), состояние FORCE LOGGING и flashback, а также ошибки и разрывы (gap) для redo destination.","when":"Как предварительная проверка перед switchover/failover, после создания нового standby и для базового мониторинга там, где брокер не используется.","caution":"Только чтение. Если FORCE_LOGGING=NO, операции NOLOGGING оставляют повреждённые блоки на standby; на primary в Data Guard должен быть включён ALTER DATABASE FORCE LOGGING. Если FLASHBACK_ON=NO, старый primary нельзя восстановить через reinstate после failover — его придётся пересоздавать.","note":"Работает и на primary; v$archive_gap имеет смысл только на физическом standby.","tags":["data guard","v$database","switchover_status","force logging","gap","role","standby"]},{"id":"dg-lag-apply-rate","task":"Измерить задержку transport/apply, скорость apply и процессы Data Guard на 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":"Показывает, насколько standby отстаёт от primary (transport lag = ещё не полученный redo, apply lag = ещё не применённый redo), когда будет применён накопившийся объём, скорость apply и какой процесс над каким sequence работает.","when":"Для мониторинга RPO/RTO, а при алерте о задержке — чтобы понять, в чём проблема: в сети (transport) или в I/O/CPU standby (apply).","caution":"Только чтение. Если datum_time старый (сильно отстаёт от time_computed), standby не получает данные от primary и значение задержки недостоверно. Проверьте, не задан ли DelayMins; намеренная задержка выглядит как apply lag.","since":"12c","note":"v$dataguard_process доступно с 12.2+. Без standby v$dataguard_stats не возвращает строк.","tags":["data guard","lag","lag","apply","transport","v$dataguard_stats","rpo"]},{"id":"dg-validate-database","task":"Проверить standby через брокер перед 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":"Проверяет готовность standby к смене роли (\"Ready for Switchover / Failover: Yes\"), количество/размер standby redo log, состояние flashback и apply, различия spfile и статические сервисы, необходимые брокеру для перезапуска экземпляров.","when":"Как обязательный шаг перед каждым плановым switchover; после создания нового standby и после изменений сети/listener.","caution":"Только чтение. Если видите \"Ready for Switchover: No\" или предупреждение о standby redo log, не продолжайте switchover. Ошибка static connect identifier означает, что брокер не сможет запустить экземпляр во время switchover, и потребуется ручное вмешательство.","since":"12c","note":"VALIDATE DATABASE доступен с 12.1+, опция SPFILE — с 12.2+, NETWORK CONFIGURATION и STATIC CONNECT IDENTIFIER — с 18c+.","tags":["data guard","dgmgrl","validate","validate","switchover","standby redo log","broker"],"untested":true},{"id":"dg-switchover","task":"Выполнить плановый switchover (DGMGRL, смена роли без потери данных)","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":"Контролируемо меняет роли primary и standby: весь redo primary передаётся и применяется на standby, старый primary перезапускается как standby, и redo apply запускается автоматически.","when":"Для планового обслуживания на площадке primary (ОС, оборудование, rolling upgrade), учений DR или переезда ЦОД.","caution":"У primary кратковременный простой; подключения приложений должны направляться на новый primary через сервисы с -role PRIMARY и FAN/TAF/AC. Долгие транзакции и большая apply lag удлиняют switchover; сначала доведите задержку почти до нуля. В RAC брокер сам управляет экземплярами; не вмешивайтесь через srvctl.","since":"12c","note":"В 12.1+ брокер выполняет все шаги одной командой; без брокера в 11g использовался ALTER DATABASE COMMIT TO SWITCHOVER в SQL (12c+: ALTER DATABASE SWITCHOVER TO <db> VERIFY).","tags":["data guard","switchover","dgmgrl","role change","planned","standby","primary"],"untested":true},{"id":"dg-failover","task":"Выполнить failover после потери 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":"Переводит standby в роль primary, когда primary недоступен; полный (complete) failover сначала применяет весь полученный redo, IMMEDIATE пропускает apply.","when":"Когда площадка, хранилище или база данных primary утрачены без возможности восстановления, а RTO не позволяет ждать switchover.","caution":"Эта смена роли необратима. Сначала убедитесь, что primary ДЕЙСТВИТЕЛЬНО недоступен; если старый primary остаётся открытым и приложение продолжает в него писать, возникнет split-brain и расхождение данных (остановите/изолируйте старый primary). В режиме MaxPerformance теряется объём данных, равный transport lag. После этого пересмотрите все standby и стратегию бэкапов для нового primary.","note":"Для восстановления старого primary см. рецепт dg-reinstate-database (требуется flashback).","tags":["data guard","failover","dgmgrl","disaster","disaster recovery","data loss","split-brain"],"untested":true},{"id":"dg-reinstate-database","task":"Восстановить старый primary как standby после 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":"Откатывает старый primary с помощью flashback database к моменту до SCN failover, преобразует его в физический standby и начинает приём redo от нового primary; пересоздавать standby с нуля не нужно.","when":"После failover, когда сервер/хранилище старого primary снова доступны; после автоматического failover FSFO (observer обычно выполняет reinstate сам).","caution":"Flashback должен быть включён на старом primary ДО failover, а flashback logs должны покрывать SCN failover (db_flashback_retention_target, размер FRA); иначе reinstate завершится ошибкой и standby придётся пересоздавать через RMAN duplicate. Данные, записанные в старый primary после failover, теряются безвозвратно.","note":"Проверка предварительного условия: столбец flashback_on в рецепте dg-readiness-sql.","tags":["data guard","reinstate","flashback","failover","old primary","standby","dgmgrl"],"untested":true},{"id":"dg-apply-transport-state","task":"Остановить / запустить redo apply или redo transport через брокер; настроить отложенное применение","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":"Через брокер постоянно останавливает/запускает redo apply (MRP) на standby или передачу redo на primary; DelayMins задерживает redo на заданное время перед применением.","when":"Для обслуживания или бэкапов на standby, плановых отключений сети, управления передачей при больших массовых загрузках на primary или для создания отложенного standby на случай случайных удалений.","caution":"Пока брокер включён, не используйте ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL в SQL; брокер восстановит состояние. Во время APPLY-OFF растут apply lag и время failover (RTO); во время TRANSPORT-OFF данные, созданные на primary, не защищены (RPO). Если FSFO включён, эти операции могут помешать failover.","note":"При DelayMins > 0 redo применяется из архивных журналов после задержки, а не через real-time apply; standby продолжает получать redo во время задержки.","tags":["data guard","apply","transport","mrp","delaymins","stop","dgmgrl"],"untested":true},{"id":"dg-fsfo-observer","task":"Включить Fast-Start Failover (FSFO) и запустить 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":"Позволяет observer автоматически выполнить failover на standby по истечении порога, если primary недоступен, и автоматически выполнить reinstate старого primary, когда он вернётся.","when":"На критичных системах с целевым RTO в минутах, без ожидания вмешательства человека.","caution":"Observer не должен работать на том же сервере/в том же ЦОД, что primary или standby; иначе FSFO не сработает при потере площадки или может принять неверное решение при сетевом разделении. Flashback должен быть включён на обеих базах. Слишком низкий порог превращает кратковременные сетевые сбои в ненужные failover. Для планового обслуживания выполните DISABLE FAST_START FAILOVER или учтите observer перед остановкой primary.","since":"12c","note":"FASTSYNC доступен с 12.2+, IN BACKGROUND и несколько observer (SHOW OBSERVER) — с 12.2+. IN BACKGROUND требует учётных данных в Oracle wallet (mkstore).","tags":["data guard","fsfo","fast-start failover","observer","automatic failover","maxavailability","dgmgrl"],"untested":true},{"id":"dg-snapshot-standby","task":"Преобразовать физический standby в snapshot standby и обратно (тестовая среда)","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":"Создаёт на standby автоматическую гарантированную точку восстановления и открывает его в режиме read-write; при обратном преобразовании выполняет flashback к этой точке и применяет redo, накопившийся от primary.","when":"Чтобы отрепетировать обновление, протестировать релиз приложения или запустить Real Application Testing (DB Replay) на производственных данных без создания отдельной копии.","caution":"Пока база является snapshot standby, redo не применяется: RPO сохраняется, но время failover (RTO) растёт вместе с накопленным redo, и она не может быть целью FSFO. Flashback logs могут заполнить FRA; следите за местом в FRA. Изменения, сделанные в snapshot, безвозвратно отбрасываются при обратном преобразовании.","note":"SQL-аналог: ALTER DATABASE CONVERT TO SNAPSHOT STANDBY / CONVERT TO PHYSICAL STANDBY (без брокера).","tags":["data guard","snapshot standby","test","restore point","flashback","convert","dgmgrl"],"untested":true},{"id":"rman-catalog-create","task":"Создать каталог восстановления RMAN и зарегистрировать базу данных","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":"Создаёт recovery catalog, хранящий метаданные бэкапов в центральной схеме независимо от срока хранения в controlfile (control_file_record_keep_time), и регистрирует целевую базу данных.","when":"Чтобы хранить историю бэкапов множества баз в одном месте, использовать бэкапы primary/standby взаимозаменяемо в Data Guard, а также для долгосрочных (KEEP) бэкапов и хранимых скриптов.","caution":"База каталога должна находиться на другом сервере, чем защищаемые ею базы, и сама должна резервироваться. Версия схемы каталога не может быть ниже самого нового используемого клиента RMAN; более новый клиент требует UPGRADE CATALOG. В Data Guard регистрируйте только primary; standby определяются по DB_UNIQUE_NAME через CONFIGURE DB_UNIQUE_NAME ... CONNECT IDENTIFIER.","note":"Держите схему каталога в отдельной базе данных; если она размещена вместе с целевой базой, авария унесёт и каталог.","tags":["rman","catalog","catalog","recovery catalog","register","backup","backup"],"untested":true},{"id":"rman-duplicate-standby","task":"Создать физический standby из активной базы данных через 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":"Копирует datafile primary по сети на сервер standby (без промежуточных файлов бэкапа), создаёт spfile, адаптированный для standby, и применяет доступные archivelog с помощью DORECOVER.","when":"При создании нового standby Data Guard или пересоздании standby, для которого невозможен reinstate.","caution":"Создаёт сетевую нагрузку и нагрузку I/O на primary; не запускайте в часы пик или ограничьте число каналов. NOFILENAMECHECK безопасен, только если standby находится на ОТДЕЛЬНОМ сервере; на том же сервере он может перезаписать файлы primary. Добавьте standby redo log (того же размера, что online redo primary, на одну группу больше на каждый thread) до или после, затем настройте брокер.","since":"12c","note":"Active duplicate с USING [COMPRESSED] BACKUPSET и SECTION SIZE доступен с 12c+; в 11g используются image copy. Если вместо ASM используется файловая система, задайте 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":"Проверить и очистить записи о бэкапах RMAN (CROSSCHECK, EXPIRED, OBSOLETE)","category":"RMAN","command":"SHOW RETENTION POLICY;\n# 1) Compare records with the physical files (missing ones become EXPIRED):\nCROSSCHECK BACKUP;\nCROSSCHECK COPY;\nCROSSCHECK ARCHIVELOG ALL;\n# 2) Review before deleting:\nLIST EXPIRED BACKUP;\nLIST EXPIRED ARCHIVELOG ALL;\nREPORT OBSOLETE;\n# 3) Clean up:\nDELETE NOPROMPT EXPIRED BACKUP;\nDELETE NOPROMPT EXPIRED COPY;\nDELETE NOPROMPT EXPIRED ARCHIVELOG ALL;\nDELETE NOPROMPT OBSOLETE;","what":"CROSSCHECK проверяет, существуют ли бэкапы из записей RMAN на диске/ленте; DELETE EXPIRED удаляет записи об отсутствующих файлах, а DELETE OBSOLETE удаляет бэкапы, более не нужные согласно политике хранения, и из записей, и с диска.","when":"Когда файлы бэкапов были удалены/перемещены на уровне ОС, когда заполняется FRA, а также как часть регулярного задания по обслуживанию бэкапов.","caution":"DELETE OBSOLETE физически удаляет файлы; сначала проверьте список через REPORT OBSOLETE и убедитесь, что политика хранения (RECOVERY WINDOW / REDUNDANCY) соответствует ожиданиям. Если запустить CROSSCHECK, когда ленточная библиотека/media manager временно недоступны, корректные бэкапы будут помечены как EXPIRED; не запускайте в этот момент DELETE EXPIRED.","note":"В Data Guard для удаления archivelog используйте CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON ALL STANDBY.","tags":["rman","crosscheck","expired","obsolete","cleanup","retention","backup"],"untested":true},{"id":"rman-validate","task":"Проверить базу данных и бэкапы на повреждения (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":"Читает datafile и backup set для обнаружения физического и (с CHECK LOGICAL) логического повреждения блоков, записывая результаты в v$database_block_corruption. SQL-запрос показывает, какой таблице/индексу принадлежит повреждённый блок.","when":"После ошибок повреждения, таких как ORA-01578/ORA-08103, после проблем с хранилищем и как регулярная (еженедельная) проверка целостности; чтобы доказать, что бэкапы действительно восстанавливаются.","caution":"Выполняет полное чтение; на больших базах создаёт высокую нагрузку I/O, поэтому запускайте вне часов пик. Запрос к dba_extents на больших базах может быть медленным. Если повреждённый блок находится во временном пространстве (TEMP) или в свободном пространстве, соответствующий объект не возвращается.","since":"11g","note":"При отсутствии повреждений запросы не возвращают строк. VALIDATE с SECTION SIZE доступен с 11g+.","tags":["rman","validate","corruption","corruption","check logical","block","ora-01578"],"untested":true},{"id":"rman-restore-preview","task":"Посмотреть, какие бэкапы использует восстановление, и проверить их (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 по данным каталога/controlfile сообщает, какие backup set/копии и какой диапазон archivelog будут использованы при восстановлении; VALIDATE фактически читает эти бэкапы и проверяет отсутствие повреждений и пропущенных файлов.","when":"Для планирования перед реальным восстановлением, чтобы доказать, что стратегия бэкапов соответствует целевому RPO, и как периодический тест восстановления.","caution":"PREVIEW смотрит только на записи; если файлы были удалены, результат вводит в заблуждение, поэтому сначала выполните CROSSCHECK или используйте RESTORE ... VALIDATE. VALIDATE читает весь бэкап; с ленты/по сети это может занять много времени. Задавайте дату через маску TO_DATE, а не в зависимости от настроек NLS сессии.","note":"PREVIEW RECALL также может запросить бэкапы, которые нужно вернуть с ленты (требуется поддержка media manager).","tags":["rman","restore","preview","validate","restore test","backup","rpo"],"untested":true},{"id":"rman-block-media-recovery","task":"Восстановить повреждённые блоки онлайн (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":"Восстанавливает только повреждённые блоки из бэкапа (или из flashback logs / standby Active Data Guard) и накатывает их redo; datafile и табличное пространство остаются онлайн, а приложение получает ошибки только при обращении к этим блокам.","when":"Когда появляется единичное повреждение блоков с ORA-01578 / ORA-01110; когда восстановление всего datafile привело бы к ненужному простою.","caution":"Требуется чистый бэкап, содержащий блок, и все archivelog с того момента до текущего; повреждение, вызванное операцией NOLOGGING (ORA-26040), так исправить нельзя — объект нужно пересоздать. Это возможность Enterprise Edition. Если не найти источник повреждения (хранилище, HBA, прошивка), оно повторится.","note":"С Active Data Guard в 11.2+ automatic block media recovery автоматически восстанавливает повреждённые блоки со standby.","tags":["rman","block recovery","corrupt block","corruption","ora-01578","recover","repair"],"untested":true},{"id":"rman-tspitr","task":"Вернуть табличное пространство к моменту в прошлом (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 восстанавливает SYSTEM/UNDO/SYSAUX и целевое табличное пространство до заданного времени во временном вспомогательном (auxiliary) экземпляре, затем подключает его обратно в целевую базу через transportable tablespace; остальная часть базы остаётся актуальной.","when":"Когда ошибочное пакетное задание или неверный DROP/TRUNCATE повредили данные только в одном табличном пространстве (или в самосогласованном наборе табличных пространств), а возможностей flashback недостаточно.","caution":"ВСЕ изменения в этом табличном пространстве и все объекты, созданные после целевого времени, теряются. Набор табличных пространств должен быть самодостаточным (никаких индексов/таблиц, секций, LOB или FK в другом табличном пространстве). Нельзя использовать для табличных пространств, содержащих объекты SYSTEM, UNDO или SYS. После TSPITR прежние бэкапы этого табличного пространства непригодны; немедленно сделайте бэкап. AUXILIARY DESTINATION требует свободного места для SYSTEM+SYSAUX+UNDO+целевого табличного пространства.","note":"Для одной таблицы rman-recover-table менее разрушителен.","tags":["rman","tspitr","tablespace","pitr","point-in-time","auxiliary","rollback"],"untested":true},{"id":"rman-recover-table","task":"Восстановить одну таблицу на момент в прошлом из бэкапа RMAN (RECOVER TABLE)","category":"RMAN","command":"# Restore under a new name (the original table is not touched):\nRECOVER TABLE hr.employees\n  UNTIL TIME \"TO_DATE('2026-01-15 03:00:00','YYYY-MM-DD HH24:MI:SS')\"\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Multitenant: a table inside a PDB\nRECOVER TABLE hr.employees OF PLUGGABLE DATABASE <pdb>\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  REMAP TABLE hr.employees:employees_rec;\n# Only produce a Data Pump dump, do not import:\nRECOVER TABLE hr.employees\n  UNTIL SCN 12345678\n  AUXILIARY DESTINATION '/u03/aux'\n  DATAPUMP DESTINATION '/u03/dump'\n  DUMP FILE 'employees_rec.dmp'\n  NOTABLEIMPORT;","what":"RMAN восстанавливает автоматический вспомогательный экземпляр до заданного времени, экспортирует таблицу (или секцию) через Data Pump и импортирует её в целевую базу под другим именем с помощью REMAP; затем вспомогательный экземпляр удаляется.","when":"Чтобы восстановить данные одной таблицы, ошибочно удалённые/изменённые или удалённые через DROP с PURGE, за пределами того, что позволяет undo retention для Flashback Table/Query.","caution":"AUXILIARY DESTINATION требует свободного места для SYSTEM, SYSAUX, UNDO и табличного пространства таблицы; операция может занять часы. Без REMAP TABLE операция завершится ошибкой, если таблица с таким же именем существует; всегда используйте REMAP или NOTABLEIMPORT, чтобы не перезаписать оригинал. Таблицы в схемах SYS/SYSTEM и в табличных пространствах SYSTEM/SYSAUX так восстановить нельзя. Требуется Enterprise Edition.","since":"12c","note":"Необходимо: полный бэкап и archivelog, покрывающие целевое время. Для секции: 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":"Создать новую PDB из 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":"Создаёт новую пустую pluggable database из шаблона seed; в той же команде задаёт локального администратора, табличное пространство по умолчанию и общий лимит размера.","when":"При открытии изолированной базы данных для нового приложения/схемы; при подготовке тестовых сред и сред разработки.","caution":"Новая PDB поднимается в состоянии MOUNTED; ей нужны OPEN и SAVE STATE (чтобы оставаться открытой после перезапуска CDB). Неверные пути FILE_NAME_CONVERT запишут файлы не в тот каталог; без OMF полный путь к файлу для DATAFILE обязателен (ORA-02236). Лицензирование: в 19c EE можно использовать 3 пользовательские PDB на CDB без опции Multitenant.","since":"12c","note":"В RAC добавьте сервис приложения к PDB через srvctl add service -pdb.","tags":["multitenant","pdb","create","create pluggable database","seed","cdb","pdb$seed"]},{"id":"pdb-clone-local","task":"Клонировать PDB в пределах той же CDB (локальный / горячий клон)","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":"Создаёт новую PDB в той же CDB, копируя datafile исходной PDB; при local undo и ARCHIVELOG может выполняться, пока источник открыт в режиме read-write (горячий клон).","when":"Чтобы создать тестовую копию / копию для разработки производственной PDB, отрепетировать обновление или патч, или быстро сделать копию перед рискованным изменением.","caution":"Копирование создаёт дополнительную нагрузку I/O на источнике и требует на целевой стороне столько же места, сколько занимает источник. В режиме shared undo или в NOARCHIVELOG источник должен быть READ ONLY (документация 19c). Клон содержит производственные данные: перед открытием проверьте маскирование, DB link, задания (DBMS_SCHEDULER) и подключения к внешним системам.","since":"12c","note":"Горячий клон при источнике в режиме read-write требует в 19c local undo + ARCHIVELOG. Для быстрой thin-копии используйте SNAPSHOT COPY (требуется подходящее хранилище).","tags":["multitenant","pdb","clone","clone","hot clone","copy","local undo"]},{"id":"pdb-clone-remote","task":"Клонировать PDB из другой CDB через DB link (удалённый клон)","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":"Копирует PDB из исходной CDB в целевую CDB по сети через DB link; промежуточные файлы и unplug не нужны.","when":"Чтобы перенести PDB на другой сервер/в другую CDB, создать тестовую копию production на отдельном сервере или перейти на CDB более нового релиза.","caution":"Исходная и целевая CDB должны быть совместимы по порядку байтов (endianness), набору символов (или целевой AL32UTF8) и опциям; PDB, клонированная в более новый релиз, сначала открывается в режиме RESTRICTED и требует datapatch/обновления. По завершении клонирования удалите DB link и common user. Горячий клон требует local undo + ARCHIVELOG на источнике.","since":"12c","note":"Для переноса (с удалением источника) используйте опцию RELOCATE 12.2+: CREATE PLUGGABLE DATABASE <pdb> FROM <pdb>@clone_link RELOCATE.","tags":["multitenant","pdb","remote clone","db link","clone","migration","relocate"]},{"id":"pdb-unplug-plug","task":"Отключить PDB (unplug) и подключить её к другой (или той же) 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":"Отсоединяет PDB от CDB и записывает её метаданные в XML-манифест (или в единый архив .pdb вместе с datafile); после проверки совместимости подключает её обратно в целевую CDB с теми же файлами.","when":"Чтобы перенести PDB в CDB более нового релиза на том же сервере (обновление через unplug/plug), для консолидации CDB или для переносов, когда клонирование по сети невозможно.","caution":"После unplug PDB недоступна в исходной CDB (простой). Файлы, подключённые с NOCOPY, теперь принадлежат целевой CDB; сначала сделайте файловую копию или бэкап RMAN, чтобы можно было откатиться при сбое. Пока нарушения типа ERROR не устранены, PDB открывается в режиме RESTRICTED. При повторном подключении к той же CDB отсутствие AS CLONE может вызвать конфликт GUID.","since":"12c","note":"Архив .pdb (12.2+) включает datafile, поэтому переносится одним файлом; при методе с XML datafile копируются отдельно.","tags":["multitenant","pdb","unplug","plug","migration","xml manifest","check_plug_compatibility"]},{"id":"pdb-open-save-state","task":"Открыть PDB и сохранить их открытыми при перезапусках CDB (SAVE STATE)","category":"Multitenant","command":"-- Open all PDBs (RAC: INSTANCES = ALL):\nALTER PLUGGABLE DATABASE ALL OPEN;\n-- ALTER PLUGGABLE DATABASE <pdb> OPEN INSTANCES = ALL;\n-- Save the current open state (opens automatically after a CDB restart):\nALTER PLUGGABLE DATABASE <pdb> SAVE STATE;\n-- Saved states:\nSELECT con_name, instance_name, state, restricted FROM dba_pdb_saved_states;\n-- Remove the saved state:\nALTER PLUGGABLE DATABASE <pdb> DISCARD STATE;\n-- If it opens RESTRICTED, find the reason:\nSELECT name, cause, type, message\nFROM   pdb_plug_in_violations\nWHERE  status <> 'RESOLVED';","what":"Открывает PDB в режиме READ WRITE и сохраняет текущее состояние открытия, чтобы при перезапуске CDB PDB автоматически открывалась в том же режиме.","when":"После создания, подключения или клонирования PDB; когда замечено, что PDB остаются в MOUNTED после перезапуска CDB.","caution":"SAVE STATE фиксирует состояние на момент выполнения команды: если запустить её при закрытой PDB, будет записано состояние \"closed\" (или запись будет удалена). В RAC состояние хранится для каждого экземпляра; при управлении через сервисы (сервисы PDB через srvctl) запуск через Clusterware предпочтительнее SAVE STATE. На standby Data Guard режим открытия зависит от роли.","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":"Задать для каждой PDB лимиты CPU, памяти, I/O и хранилища","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":"С помощью параметров уровня PDB ограничивает, сколько CPU, SGA/PGA, IOPS/MBPS и общего хранилища PDB может использовать из ресурсов CDB; не даёт PDB-\"шумным соседям\" влиять на остальные.","when":"В консолидированных CDB, при размещении приложений с разными SLA в одной CDB или когда видно, что одна PDB исчерпывает весь сервер.","caution":"sga_target PDB должен быть меньше sga_target CDB; слишком низкие значения вызывают ORA-04031 и проблемы производительности. Сумма sga_min_size не должна резервировать всю SGA CDB. Чтобы cpu_count в PDB действовал, в CDB должен быть активен план resource manager. При достижении MAXSIZE PDB получает ORA-65114.","since":"12c","note":"23ai требует, чтобы sga_min_size был ниже 20% sga_target (ORA-56746), в 19c лимит — 50%. MAX_IOPS/MAX_MBPS доступны с 12.2+, параметры памяти PDB — с 12.2+.","tags":["multitenant","pdb","resource","resource","cpu_count","sga_target","maxsize"]},{"id":"pdb-container-context","task":"Проверить, в каком контейнере вы находитесь, и запросить все PDB из 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":"Показывает контейнер, к которому подключена сессия; из root представления CDB_ и конструкция CONTAINERS() собирают информацию словаря из всех открытых PDB одним запросом.","when":"Чтобы убедиться, что вы в нужном контейнере, перед DDL/изменением параметров, и чтобы составить инвентаризацию пространства, пользователей и объектов по всем PDB.","caution":"Только чтение. Не создавайте случайно объекты приложений или локальных пользователей в root (CDB$ROOT); ALTER SYSTEM в root влияет на всю CDB. Представления CDB_ и CONTAINERS() возвращают только открытые PDB; PDB в состоянии MOUNTED в результат не попадают.","since":"12c","note":"Для смены контейнера: ALTER SESSION SET CONTAINER = <pdb>; (см. rosetta \"Change the active database\").","tags":["multitenant","container","con_name","cdb_","containers","pdb","root"]},{"id":"pdb-refreshable-clone","task":"Создать refreshable PDB, обновить её и преобразовать в независимую 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":"Создаёт копию, которая поддерживается в актуальном состоянии за счёт периодического или ручного применения redo исходной PDB; открывается READ ONLY для отчётов и становится независимой PDB в режиме read-write после REFRESH MODE NONE.","when":"Для регулярно обновляемой копии production для отчётов/тестов, миграции PDB с минимальным простоем (финальное обновление + REFRESH MODE NONE) или как \"дешёвый standby уровня PDB\".","caution":"Во время обновления PDB должна быть закрыта; пока она открыта READ ONLY, автоматическое обновление ожидает. Archivelog, нужные для обновления, должны быть доступны на источнике. REFRESH MODE NONE необратим; копию больше нельзя обновлять. Требует local undo + ARCHIVELOG на источнике. Не заменяет Data Guard для полноценного DR.","since":"12c","note":"12.2+. После REFRESH MODE NONE клон становится обычной PDB и может быть открыт READ WRITE; отменить это нельзя.","tags":["multitenant","pdb","refreshable","refresh","refresh","reporting","clone"]}]},"recipes/oracle-ops.json":{"engineId":"oracle","engine":"Oracle","order":2,"recipes":[{"id":"awr-snapshot-report","task":"Сделать снимок AWR и сформировать отчёт AWR между двумя снимками","category":"Производительность","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":"Формирует отчёт между двумя снимками AWR, охватывающими проблемный интервал: профиль нагрузки, основные wait events, топ SQL и статистику экземпляра. Табличная функция позволяет получать отчёт из скрипта или задания планировщика.","when":"При анализе замедления в конкретном временном интервале, для сравнения до/после нагрузочного теста или пакетной обработки и для отчётов по ёмкости.","caution":"Требует лицензии Diagnostics Pack (CONTROL_MANAGEMENT_PACK_ACCESS='DIAGNOSTIC' или 'DIAGNOSTIC+TUNING'); без неё установите параметр в 'NONE' и используйте Statspack. Отчёт не может охватывать перезапуск экземпляра. Интервалы длиннее часа усредняют данные и скрывают кратковременные всплески; держите окно узким.","note":"Для AWR уровня PDB (12.2+) проверьте AWR_PDB_AUTOFLUSH_ENABLED=TRUE и AWR_SNAPSHOT_TIME_OFFSET/AWR_PDB_MAX_PARALLEL_SLAVES; отчёт, снятый внутри PDB, показывает только данные этой PDB. Интервал/хранение: 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) между двумя периодами","category":"Производительность","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":"Сопоставляет два диапазона снимков и показывает процентные различия в DB time, wait events, профиле нагрузки и статистике по каждому SQL. Прямо отвечает на вопрос \"вчера было нормально, сегодня медленно\".","when":"Анализ регрессии после обновления/патча, изменения параметров, развёртывания или сбора статистики; сравнение одного и того же временного интервала в два разных дня.","caution":"Требует лицензии Diagnostics Pack. Сравниваемые периоды должны быть одинаковой длины и с похожей нагрузкой, иначе разница вводит в заблуждение. Для сравнения разных баз данных (например, prod и test) перенесите данные AWR через awrextr.sql/awrload.sql и передайте соответствующий dbid2.","tags":["awr diff","comparison","compare period","awrddrpt","regression","performance","diagnostics pack"]},{"id":"ash-top-activity","task":"Топ wait events и топ SQL из ASH за последние минуты","category":"Производительность","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":"По выборкам Active Session History показывает в процентах, какие wait events и какие SQL-операторы потребили DB time за последние минуты; позволяет диагностировать инцидент, не дожидаясь снимка AWR.","when":"Во время текущего замедления, для кратковременных всплесков, которые теряются в средних значениях AWR, и чтобы увидеть, чего ожидала конкретная сессия или SQL.","caution":"Запросы к V$ACTIVE_SESSION_HISTORY и DBA_HIST_ACTIVE_SESS_HISTORY требуют лицензии Diagnostics Pack. ASH основан на выборках: число выборок оценивает время, а не является точным измерением, и очень короткие SQL могут не попасть в выборку. Буфер ASH в памяти на нагруженных системах хранит данные лишь за несколько часов.","tags":["ash","active session history","top wait","top sql","wait","ashrpt","real-time analysis","diagnostics pack"]},{"id":"addm-task-report","task":"Выполнить анализ ADDM и получить отчёт","category":"Производительность","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 анализирует DB time между двумя снимками AWR и сообщает об узких местах с их влиянием (findings) и конкретными рекомендациями.","when":"Чтобы перед чтением отчёта AWR получить краткую сводку \"куда смотреть\", или чтобы проанализировать произвольный диапазон помимо автоматического запуска ADDM после каждого снимка.","caution":"Требует лицензии Diagnostics Pack. Не применяйте рекомендации ADDM (увеличение параметров, запуск SQL Tuning Advisor и т. п.) вслепую; проверяйте рекомендации по памяти по разделам advisory в AWR. Диапазон не может охватывать перезапуск.","tags":["addm","dbms_addm","advisor","finding","finding","recommendation","diagnostics pack","performance"]},{"id":"sql-monitor-report","task":"Получить отчёт SQL Monitor (выполняющийся или завершённый долгий SQL)","category":"Производительность","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 показывает для каждого шага плана выполнения долгого или параллельного SQL фактическое число строк, затраченное время, использование памяти/temp и активность ASH; можно следить за ходом, пока SQL ещё выполняется.","when":"Когда нужно понять, завис ли пакетный/отчётный запрос и сколько осталось, для анализа распределения параллельного запроса (PX skew) и для поиска расхождений между оценочным (E-Rows) и фактическим числом строк.","caution":"Требует лицензии Tuning Pack (Diagnostics Pack — обязательное условие). По умолчанию отслеживаются только SQL, потребившие 5 s CPU/I-O, или выполняющиеся параллельно; для остальных используйте хинт /*+ MONITOR */. Очень большие планы (_sqlmon_max_planlines, по умолчанию 300) не отслеживаются. Записи хранятся в памяти ограниченное время; в 12c+ старые отчёты доступны также из 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":"Включить/выключить трассировку 10046 для сессии и обобщить её через TKPROF","category":"Производительность","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":"Расширенная SQL-трассировка (10046) записывает в trace-файл время parse/execute/fetch, wait events и значения bind-переменных для каждого SQL, выполняемого сессией; TKPROF превращает его в читаемый отчёт с агрегацией по SQL.","when":"Когда AWR/ASH недостаточно и нужно доказать от начала до конца, на что тратит время одна пользовательская операция; при проблемах с round-trip между приложением и базой данных (SQL*Net).","caution":"Лицензия не требуется. Trace-файлы быстро растут и создают накладные расходы для целевой сессии; держите трассировку короткой и всегда отключайте её (проверьте DBA_ENABLED_TRACES на предмет оставшихся). binds=TRUE записывает в trace-файл чувствительные данные (номера документов, номера карт и т. п.). ALTER SESSION SET EVENTS требует привилегии ALTER SESSION (иначе ORA-01031). TKPROF по умолчанию не показывает значения bind-переменных; сохраняйте исходный файл .trc.","note":"Трассировка пула подключений или на уровне сервиса создаёт несколько файлов; объедините их через trcsess (trcsess output=combined.trc clientid=<id> *.trc), затем запустите TKPROF.","tags":["10046","trace","tkprof","dbms_monitor","sql trace","wait","bind","session tracing"]},{"id":"xplan-allstats-last","task":"Показать фактический план выполнения со статистикой row source (ALLSTATS LAST)","category":"Производительность","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 — это оценка; здесь показывается фактически выполненный план со Starts, E-Rows (оценка), A-Rows (факт), A-Time, Buffers и использованием памяти по каждому шагу. Это самый прямой способ найти шаг, с которого начинается ошибка оценки кардинальности.","when":"При настройке одного медленного SQL, чтобы доказать необходимость статистики/гистограмм и измерить эффект от хинта или изменения индекса.","caution":"Лицензия не требуется. При SET SERVEROUTPUT ON sql_id=>NULL покажет вызов DBMS_OUTPUT как последний курсор; сначала выключите его (OFF). Не включайте statistics_level=ALL на уровне системы; это даёт заметные накладные расходы CPU. Хинт меняет текст SQL, поэтому он получает новый sql_id. Пользователю нужен доступ на чтение к 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":"Создать и выполнить задачу SQL Tuning Advisor и прочитать её рекомендации","category":"Производительность","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":"Запускает оптимизатор в расширенном режиме \"tuning mode\" для SQL и выдаёт рекомендации по отсутствующей статистике, новым индексам, SQL profile, альтернативным планам и переработке SQL; проверяет рекомендацию по профилю тестовыми выполнениями.","when":"Когда для одного проблемного SQL нужно быстрое второе мнение или когда ищется исправление регрессии плана через SQL profile без изменения кода.","caution":"Требует лицензии Tuning Pack. Область COMPREHENSIVE может выполнять SQL для тестирования (для DML — только часть запроса); в production задавайте time_limit. Рекомендации по индексам не учитывают остальную нагрузку; оценивайте стоимость DML отдельно. Пользователю нужна привилегия ADVISOR.","tags":["sql tuning advisor","dbms_sqltune","tuning task","recommendation","sql profile","index recommendation","tuning pack"]},{"id":"sql-profile-accept","task":"Принять, отключить и удалить SQL profile","category":"Производительность","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":"SQL profile даёт оптимизатору дополнительные поправки кардинальности/селективности (OPT_ESTIMATE и аналогичные), чтобы он построил лучший план без изменения текста SQL; с FORCE_MATCH применяется и к операторам, отличающимся только литералами.","when":"Когда SQL нельзя изменить (коробочное приложение), когда STA рекомендует проверенное улучшение, или как быстрое исправление регрессии плана.","caution":"Требует лицензии Tuning Pack. Профиль не \"фиксирует\" план; он корректирует оценки, поэтому план всё ещё может измениться при изменении распределения данных. Если стабильность плана обязательна, используйте SQL plan baseline. Если существуют оба, оптимизатор выбирает среди принятых планов baseline. Перед принятием всегда читайте результаты проверки (раздел отчёта 'Validation results').","tags":["sql profile","accept_sql_profile","dbms_sqltune","force_match","plan fix","dba_sql_profiles","tuning pack"]},{"id":"spm-load-from-cursor-cache","task":"Загрузить хороший план из cursor cache как SQL plan baseline","category":"Производительность","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":"С помощью SQL Plan Management делает выбранный план принятым (ACCEPTED) baseline; после этого оптимизатор использует только принятые планы baseline, а вновь найденные планы откладывает до их эволюции (evolve).","when":"Чтобы вернуться к заведомо хорошему плану при регрессии плана и защитить планы критичных SQL перед обновлением или изменением статистики.","caution":"В Enterprise Edition дополнительная лицензия не нужна (в SE2 ограничено одним планом baseline на SQL). Требует ADMINISTER SQL MANAGEMENT OBJECT. Baseline применяется к точно такому же тексту SQL (сигнатуре); для SQL с литералами он не помогает. Если индекс, используемый планом, удалён, план не может быть воспроизведён (REPRODUCED=NO), и оптимизатор возвращается к выбору по стоимости. OPTIMIZER_USE_SQL_PLAN_BASELINES должен быть TRUE (по умолчанию).","note":"Если хорошего плана больше нет в cursor cache, загрузите его из AWR: в 12.2+ DBMS_SPM.LOAD_PLANS_FROM_AWR(begin_snap, end_snap, basic_filter => q'[sql_id='<sql_id>']'); в более ранних релизах — через SQL Tuning Set с 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":"Эволюция непринятых планов SQL plan baseline","category":"Производительность","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":"При наличии baseline новые планы, найденные оптимизатором, сохраняются с ACCEPTED=NO. Задача evolve выполняет этих кандидатов в тестовом режиме против принятого плана и принимает только явно лучшие.","when":"Для SQL с baseline, когда новый индекс или новая статистика могли дать лучший план; чтобы принять решение вручную, не дожидаясь автоматической SYS_AUTO_SPM_EVOLVE_TASK (12c+).","caution":"Требует Enterprise Edition. Evolve фактически выполняет SQL (для DML — только часть запроса); для тяжёлых SQL задавайте time_limit и избегайте часов пик. Требует ADMINISTER SQL MANAGEMENT OBJECT и ADVISOR. SQL, разобранный под SYS, нельзя подвергнуть evolve (ORA-16963); запускайте от пользователя приложения.","since":"Oracle 12c","note":"Функция DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE из версий до 12c объявлена устаревшей; в 12c+ используется API на основе задач (CREATE/EXECUTE/IMPLEMENT_EVOLVE_TASK). Поведение автоматической задачи: 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":"Зафиксировать, отключить или удалить план SQL plan baseline","category":"Производительность","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":"Управляет атрибутами планов baseline: план FIXED предпочитается другим принятым планам и останавливает автоматическую эволюцию новых планов; ENABLED=NO отключает план без удаления; DROP удаляет его безвозвратно.","when":"Чтобы жёстко закрепить план для критичного SQL, отключить ошибочно принятый плохой план или очистить ненужные baseline.","caution":"Требует Enterprise Edition. Планы FIXED могут деградировать по мере роста данных и мешают оптимизатору использовать лучший план; периодически пересматривайте их. SQL, у которого все планы ENABLED=NO, оптимизируется свободно. DROP необратим; сначала сохраните копию в staging-таблицу через DBMS_SPM.PACK_STGTAB_BASELINE.","tags":["spm","baseline","fixed","alter_sql_plan_baseline","drop_sql_plan_baseline","disable plan","display_sql_plan_baseline"]},{"id":"bind-sensitivity-acs-check","task":"Проверить bind peeking и состояние adaptive cursor sharing","category":"Производительность","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":"При первом hard parse оптимизатор выбирает план, подсматривая значение bind-переменной. Adaptive cursor sharing может создавать разные дочерние курсоры/планы для разных значений bind на столбцах с гистограммами; эти запросы показывают, является ли курсор bind-sensitive/aware, какое значение было подсмотрено и почему создавались дочерние курсоры.","when":"При жалобах \"один и тот же SQL то быстрый, то медленный\", при неравномерном распределении данных и при взрывном росте числа дочерних курсоров (high version count).","caution":"Лицензия не требуется; только чтение. V$SQL_BIND_CAPTURE заполняется выборочно (по умолчанию примерно раз в 15 минут) и содержит только bind из WHERE/HAVING; это не значение каждого выполнения. ACS делает курсор bind-aware лишь после нескольких неудачных выполнений. Для критичных SQL загрузка нескольких планов в SQL plan baseline более детерминирована, чем расчёт на ACS.","since":"Oracle 11g","note":"Adaptive cursor sharing появился в 11g. Для анализа high version count столбец REASON (XML) в V$SQL_SHARED_CURSOR даёт подробную причину.","tags":["bind peeking","adaptive cursor sharing","acs","bind variable","v$sql_bind_capture","child cursor","version count"]},{"id":"top-sql-awr-history","task":"Найти топ SQL по затраченному времени/CPU за прошлый период (AWR + V$SQLAREA)","category":"Производительность","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":"Суммирует столбцы *_DELTA в DBA_HIST_SQLSTAT, чтобы вывести самые дорогие SQL в диапазоне снимков с разбивкой по plan_hash_value, что также выявляет смену плана для одного и того же SQL. Запрос к V$SQLAREA даёт накопленные значения для того, что ещё находится в кэше.","when":"Для вопросов \"что нагружало систему прошлой ночью между 02:00 и 04:00?\", для выявления смены планов (тот же sql_id, другой plan_hash_value) и для составления списка кандидатов на настройку.","caution":"Запросы к DBA_HIST_* требуют лицензии Diagnostics Pack; к V$SQLAREA — нет. AWR хранит только топ SQL (по умолчанию top-N), поэтому небольшие, но очень частые операторы могут отсутствовать. Значения V$SQLAREA теряются при вытеснении курсора из кэша. Дельты остаются корректными после перезапуска, но запрос по кэшу начинает с нуля.","tags":["top sql","dba_hist_sqlstat","v$sqlarea","elapsed","cpu","most expensive query","plan change","diagnostics pack"]},{"id":"stats-stale-prefs","task":"Найти устаревшую статистику и задать параметры сбора статистики для таблиц (prefs)","category":"Статистика","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":"Статистика становится устаревшей, когда доля изменений превышает STALE_PERCENT (по умолчанию 10%). Preferences сохраняют для таблицы параметры выборки, гистограмм, параллелизма и порогов устаревания, чтобы ночная автоматическая задача сбора статистики применяла те же правила.","when":"Когда порог 10% на больших таблицах срабатывает слишком поздно, когда для определённых столбцов всегда нужна гистограмма, и чтобы задать стратегию сбора в словаре, а не в скриптах.","caution":"Лицензия не требуется. Явные параметры в ручном вызове GATHER переопределяют preferences. Изменение METHOD_OPT добавляет или удаляет гистограммы и может изменить планы. DBA_TAB_MODIFICATIONS актуально только после flush (автоматический flush — примерно раз в 15 минут).","tags":["stale stats","stale_percent","set_table_prefs","dba_tab_statistics","dba_tab_modifications","statistics preference","gather stale"]},{"id":"stats-lock-unlock","task":"Заблокировать и разблокировать статистику таблицы/схемы","category":"Статистика","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":"Блокирует статистику от изменения; автоматическая задача пропускает заблокированные объекты, а вызов GATHER на уровне таблицы вызывает ORA-20005. force=>TRUE собирает статистику без снятия блокировки.","when":"Чтобы сохранить репрезентативную статистику на staging/изменчивых таблицах, которые в течение дня очищаются и заполняются заново, защитить планы критичной таблицы от изменений статистики или сохранить значения, заданные через DBMS_STATS.SET_TABLE_STATS.","caution":"Лицензия не требуется. Заблокированная статистика со временем расходится с реальностью и вызывает ошибки кардинальности; регулярно формируйте отчёт по заблокированным объектам. Блокировка схемы не распространяется на таблицы, добавленные в схему позже. Для заблокированной таблицы CREATE/REBUILD INDEX также не собирает статистику индекса автоматически.","tags":["lock stats","lock statistics","unlock_table_stats","lock_schema_stats","ora-20005","stattype_locked","dbms_stats"]},{"id":"stats-restore-history","task":"Восстановить статистику на момент в прошлом","category":"Статистика","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 перед каждым сбором сохраняет предыдущую статистику в SYSAUX (по умолчанию 31 день). Процедуры RESTORE_* возвращают статистику таблицы/схемы к состоянию на заданное время; с no_invalidate=>FALSE курсоры сразу разбираются заново.","when":"Как самый быстрый откат, когда после ночного сбора статистики возникла регрессия плана, и чтобы проверить, вызвано ли изменение статистикой.","caution":"Лицензия не требуется. Заблокированную статистику восстановить нельзя (сначала разблокируйте). Следующий автоматический сбор после восстановления вернёт новую статистику; для постоянного решения задайте preferences таблицы, заблокируйте статистику или используйте SQL plan baseline. Длительный срок хранения увеличивает SYSAUX (таблицы WRI$_OPTSTAT_*).","tags":["restore stats","restore statistics","restore_table_stats","dba_tab_stats_history","stats history","plan regression","dbms_stats"]},{"id":"stats-pending-publish","task":"Собрать статистику как отложенную (pending), протестировать и опубликовать","category":"Статистика","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":"При preference PUBLISH=FALSE собранная статистика записывается в отложенную область, а не в словарь, поэтому другие сессии не затрагиваются. Планы проверяются в тестовой сессии с optimizer_use_pending_statistics, затем статистика публикуется или отбрасывается одной командой.","when":"Чтобы без риска проверить влияние обновлённой статистики на планы критичных OLTP-таблиц в production, и для контролируемого переключения после крупной загрузки данных.","caution":"Лицензия не требуется. Пока остаётся PUBLISH=FALSE, статистика, собранная автоматической задачей, тоже накапливается как отложенная и никогда не публикуется; не забудьте вернуть preference в TRUE. EXPLAIN PLAN — это оценка; по возможности выполните запрос и проверьте 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":"Настроить инкрементальную статистику для секционированной таблицы","category":"Статистика","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":"В инкрементальном режиме для каждой секции хранится synopsis; глобальная статистика (уровня таблицы), в частности NDV, вычисляется слиянием synopsis без повторного сканирования всей таблицы. Повторно читаются только изменённые секции.","when":"На больших (масштаба TB) таблицах с секционированием range/interval, где новые данные попадают только в последние секции, когда сбор глобальной статистики не укладывается в окно обслуживания.","caution":"Собственной лицензии нет, но уже требуется опция Partitioning (платная, EE). Synopsis занимают место в SYSAUX (формат HyperLogLog в 12.2+ значительно компактнее). Первый сбор — полное сканирование; запускайте его в окне обслуживания. Установка ESTIMATE_PERCENT в любое значение, кроме AUTO_SAMPLE_SIZE, отключает инкрементальный режим.","since":"Oracle 11g","note":"Preferences INCREMENTAL_STALENESS и INCREMENTAL_LEVEL добавлены в 12c; в 12.2+ формат synopsis управляется APPROXIMATE_NDV_ALGORITHM (HYPERLOGLOG).","tags":["incremental stats","synopsis","partition statistics","global stats","incremental_staleness","dbms_stats","large table"]},{"id":"partition-split-merge-online","task":"Разделить и объединить секции онлайн","category":"Секционирование","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 делит секцию по граничному значению на две (12c+: на несколько); MERGE объединяет смежные секции. С ONLINE DML продолжается во время операции; с UPDATE INDEXES локальные и глобальные индексы не становятся UNUSABLE.","when":"Чтобы разделить разросшуюся секцию MAXVALUE/\"прочее\", объединить мелкие старые секции перед архивированием или изменить схему секционирования (помесячную на поквартальную).","caution":"Требует опции Partitioning (платная, EE); ONLINE требует Enterprise Edition. SPLIT/MERGE физически перезаписывает данные: на больших секциях это медленно и создаёт redo/undo и потребность во временном пространстве. Без ONLINE таблица блокируется для DML на время операции; без UPDATE INDEXES глобальные индексы становятся UNUSABLE. Разделение пустой секции выполняется быстро (fast split).","since":"Oracle 12.2","note":"ONLINE SPLIT появился в 12.2, ONLINE MERGE — в 18c; оба доступны в 19c. В более старых релизах уберите ключевое слово ONLINE и выполняйте операцию в окне обслуживания.","tags":["split partition","merge partition","add partition","split a partition","merge partitions","update indexes","online","partitioning"]},{"id":"partition-exchange","task":"Массовая загрузка или архивирование данных через exchange partition","category":"Секционирование","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":"Меняет местами в словаре сегмент секции и сегмент обычной таблицы; миллионы строк \"входят\" в таблицу или \"уходят\" из неё в архив за секунды. FOR EXCHANGE WITH TABLE создаёт staging-таблицу с гарантированной совместимостью столбцов и скрытых столбцов.","when":"Для массовых загрузок DWH/ETL, выноса старой секции в архивную таблицу и подготовки индексов и статистики без влияния на рабочую таблицу во время загрузки.","caution":"Требует опции Partitioning (платная, EE). WITHOUT VALIDATION не проверяет, что строки staging укладываются в границы секции; неверные данные остаются в неверной секции, и запросы могут возвращать неверные результаты; если не уверены, используйте WITH VALIDATION (полное сканирование). Без UPDATE GLOBAL INDEXES глобальные индексы становятся UNUSABLE. Состояния ограничений Unique/PK должны совпадать с обеих сторон.","note":"CREATE TABLE ... FOR EXCHANGE WITH TABLE появился в 12.2; до этого создавайте staging-таблицу через CREATE TABLE ... AS SELECT * FROM <table> WHERE 1=0 и вручную проверяйте различия скрытых/виртуальных столбцов.","tags":["exchange partition","staging","for exchange with table","bulk load","archive","update global indexes","partitioning"]},{"id":"partition-drop-truncate-gidx","task":"Выполнить truncate/drop секции с сохранением работоспособности глобальных индексов","category":"Секционирование","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":"Мгновенно удаляет старые данные на уровне секции вместо построчного DELETE. В 12c+ UPDATE INDEXES лишь помечает глобальные индексы в словаре (асинхронное обслуживание) вместо обновления каждой записи; индекс сразу остаётся пригодным, а осиротевшие записи очищаются позже.","when":"При удалении старых периодов по политике хранения данных, в заданиях ILM/purge и чтобы избежать нагрузки undo/redo от DELETE на больших таблицах.","caution":"Требует опции Partitioning (платная, EE). DROP/TRUNCATE необратимы (flashback table не возвращает секцию); сначала сделайте бэкап или архивируйте через exchange. Если забыть UPDATE INDEXES, глобальные индексы станут UNUSABLE, и запросы могут получить ORA-01502. Операция завершится ошибкой, если на таблицу ссылаются дочерние таблицы (FK). В interval-таблицах последнюю range-секцию (transition) удалить нельзя.","since":"Oracle 12c","tags":["drop partition","truncate partition","update indexes","orphaned_entries","cleanup_gidx","global index","data purge","partitioning"]},{"id":"interval-partitioning","task":"Создать interval-секционирование и преобразовать существующую range-таблицу в interval","category":"Секционирование","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-секционирование автоматически создаёт новую секцию при поступлении первой строки за последней границей, устраняя ошибки ORA-14400 из-за забытой секции на следующий месяц. SET INTERVAL преобразует существующую range-таблицу без перемещения данных.","when":"Для таблиц, растущих по дате, таких как временные ряды, журналы и заказы, и чтобы сократить скрипты обслуживания секций.","caution":"Требует опции Partitioning (платная, EE). Ключ секционирования должен быть одним столбцом NUMBER/DATE/TIMESTAMP; секцию MAXVALUE нужно предварительно удалить. Ошибочная или экстремальная дата (например, год 9999) открывает далёкую секцию и искажает нумерацию секций; при узких интервалах можно приблизиться к лимиту в 1,048,575 секций (ORA-14300), поэтому проверяйте даты в приложении. Автоматические секции создаются в табличном пространстве таблицы по умолчанию (направляйте их через 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":"Проверить ключи секционирования и использование partition-wise join","category":"Секционирование","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":"Две таблицы, одинаково секционированные по ключу соединения, можно соединять попарно по секциям (full partition-wise join), что резко снижает трафик перераспределения и потребление памяти в параллельных запросах. Форма плана показывает, используется ли PWJ.","when":"При проектировании крупных соединений fact-fact или fact-dimension в DWH, когда параллельные запросы показывают интенсивное перераспределение PX (PX Deq Credit: send blkd), и при проверке стратегии секционирования.","caution":"Лицензия: опция Partitioning (платная, EE); параллельные запросы требуют Enterprise Edition. EXPLAIN PLAN — это оценка; для фактического плана используйте DISPLAY_CURSOR/SQL Monitor. Full PWJ требует одинакового числа секций (для hash рекомендуется степень двойки) и совпадающих типов данных ключа. PWJ может встречаться и в последовательных планах, но реальный выигрыш — при параллельном выполнении.","tags":["partition-wise join","pwj","equi-partitioning","hash partition","parallel query","dba_part_key_columns","partitioning"]},{"id":"online-redefinition-flow","task":"Переопределить таблицу онлайн через DBMS_REDEFINITION (полный процесс)","category":"Онлайн-операции","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":"Изменяет структуру таблицы (секционирование, типы/порядок столбцов, табличное пространство, сжатие, настройки LOB) без остановки приложения: данные копируются в промежуточную таблицу, изменения синхронизируются через MV log, а в конце имена меняются местами под очень короткой блокировкой.","when":"Для изменений, которые ALTER TABLE не может выполнить онлайн, например секционирование большой несекционированной таблицы без простоя, изменение типа столбца или преобразование BasicFile LOB в SecureFiles.","caution":"Требует Enterprise Edition (плюс опция Partitioning, если целевая таблица секционирована). Процессу нужно вдвое больше места, чем занимает таблица, и он создаёт дополнительные накладные расходы redo и MV log. FINISH берёт кратковременную эксклюзивную блокировку; долгие транзакции заставляют его ждать (dml_lock_timeout). Всегда проверяйте ошибки COPY_TABLE_DEPENDENTS в DBA_REDEFINITION_ERRORS; FK могут быть скопированы как NOVALIDATE. Сначала отрепетируйте в тестовой среде.","note":"Параметр dml_lock_timeout добавлен в 12c. В 12.2+ отслеживайте ход выполнения в V$ONLINE_REDEF. Для простого переноса/изменения сжатия вместо DBMS_REDEFINITION достаточно ALTER TABLE ... MOVE ONLINE.","tags":["dbms_redefinition","online redefinition","redefinition","zero downtime","convert to partitioned","can_redef_table","finish_redef_table","enterprise edition"]},{"id":"table-move-online","task":"Перенести таблицу или секцию онлайн (ALTER TABLE MOVE ONLINE)","category":"Онлайн-операции","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":"Перезаписывает сегмент таблицы: уменьшает фрагментацию и high water mark, меняет табличное пространство, применяет сжатие. С ONLINE DML не блокируется во время операции, а индексы обслуживаются автоматически и остаются VALID.","when":"Чтобы вернуть пространство после массовых удалений, перенести таблицу в новое табличное пространство/хранилище и сжать старые секции в архивное табличное пространство.","caution":"Требует Enterprise Edition; ROW STORE COMPRESS ADVANCED требует опции Advanced Compression (платная). Операции нужно дополнительное место, равное размеру таблицы, и она создаёт большой объём redo. В начале и в конце берутся кратковременные блокировки; долгие открытые транзакции заставляют её ждать. MOVE без ONLINE или UPDATE INDEXES оставляет все индексы в состоянии UNUSABLE.","since":"Oracle 12.2","note":"MOVE ONLINE на уровне таблицы появился в 12.2; MOVE PARTITION ONLINE на уровне секции существует с 12.1. В 12.1 и ранее для таблиц используйте DBMS_REDEFINITION.","tags":["move online","alter table move","move table","reorganization","high water mark","compression","enterprise edition"]},{"id":"tde-keystore-setup","task":"Создать и открыть keystore TDE, задать мастер-ключ и сделать бэкап (19c+)","category":"Безопасность","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":"Настраивает программный keystore для Transparent Data Encryption в united mode (все PDB используют keystore CDB), создаёт мастер-ключ шифрования и включает auto-login, чтобы keystore открывался после перезапуска. В 19c+ рекомендуемый способ — WALLET_ROOT/TDE_CONFIGURATION вместо ENCRYPTION_WALLET_LOCATION в sqlnet.ora.","when":"Как первый шаг перед шифрованием табличных пространств/столбцов в проектах по соответствию требованиям, предусматривающих шифрование данных при хранении (GDPR, PCI-DSS, ISO 27001).","caution":"On-premises требует опции Advanced Security (платная); в Oracle Cloud (OCI/Autonomous) она включена в лицензирование. WALLET_ROOT — статический параметр, требующий перезапуска экземпляра (простой). При потере keystore и его пароля зашифрованные данные становятся НАВСЕГДА нечитаемыми; храните бэкапы keystore в защищённом месте отдельно от бэкапов базы данных. Любой, у кого есть файл auto-login (cwallet.sso), может открыть базу данных; ограничьте права на файл и при необходимости используйте LOCAL AUTO_LOGIN. В Data Guard скопируйте keystore и на standby.","since":"Oracle 18c","note":"WALLET_ROOT и TDE_CONFIGURATION появились в 18c и стандартны в 19c; способ с ENCRYPTION_WALLET_LOCATION в sqlnet.ora из 12c-18c объявлен устаревшим в 19c и не поддерживается в 23ai. В 23ai алгоритм по умолчанию — AES256; шифрование с GOST и SEED не поддерживается, а расшифровка с ними объявлена устаревшей. WALLET_ROOT — статический параметр и требует перезапуска экземпляра.","tags":["tde","keystore","wallet","master key","encryption","advanced security","wallet_root","administer key management"],"untested":true},{"id":"tde-tablespace-encryption","task":"Зашифровать табличные пространства через TDE (новые и существующие, онлайн/офлайн)","category":"Безопасность","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":"Шифрование табличного пространства шифрует каждый блок, записываемый в datafile; в buffer cache данные хранятся в открытом виде, поэтому изменения приложения или SQL не нужны. Онлайн-преобразование создаёт зашифрованную копию datafile и переключается на неё, пока продолжается DML; офлайн-преобразование шифрует блоки на месте.","when":"Чтобы зашифровать при хранении табличные пространства приложений с чувствительными данными, перенести данные в новое зашифрованное табличное пространство или закрыть замечание аудита.","caution":"On-premises требует опции Advanced Security (платная). Онлайн-преобразованию нужно дополнительное место, равное размеру шифруемых datafile, и оно создаёт высокую нагрузку I/O и redo; standby Data Guard выполняет ту же работу. Офлайн-преобразование на standby Data Guard нужно запускать отдельно. SYSTEM/SYSAUX/UNDO можно шифровать онлайн в 12.2+, но это требует особой осторожности. Бэкапы зашифрованных табличных пространств нельзя восстановить без keystore.","since":"Oracle 12.2","note":"Онлайн/офлайн-преобразование табличных пространств появилось в 12.2. Для автоматического шифрования новых табличных пространств в 19c используется TABLESPACE_ENCRYPTION=AUTO_ENABLE (19.16+ RU) или ENCRYPT_NEW_TABLESPACES=ALWAYS. Сначала keystore должен быть открыт и мастер-ключ задан (см. 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":"Создать политику Unified Auditing, включить её с условием, просмотреть записи, удалить","category":"Безопасность","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 собирает все записи аудита в единый журнал (схема AUDSYS, UNIFIED_AUDIT_TRAIL). Политики определяются по действию над объектом, системной привилегии или действию; условие WHEN и списки пользователей BY/EXCEPT уменьшают шум.","when":"Чтобы фиксировать доступ к чувствительным таблицам, отслеживать привилегированный DDL и изменения привилегий, видеть неудачные попытки входа и выполнять требования аудита.","caution":"Дополнительная лицензия не нужна. Аудит SELECT на нагруженных таблицах порождает много записей и рост SYSAUX; сужайте его условиями и EXCEPT и настройте архивирование/очистку через DBMS_AUDIT_MGMT (CLEAN_AUDIT_TRAIL, SET_LAST_ARCHIVE_TIMESTAMP). Включённую политику нельзя удалить (ORA-46361); сначала выполните NOAUDIT. Изменения политики действуют в новых сессиях. 19c по умолчанию работает в mixed mode, поэтому могут записываться и традиционные записи AUDIT_TRAIL.","since":"Oracle 12c","note":"В 23ai традиционный аудит не поддерживается; существует только unified auditing, а FLUSH_UNIFIED_AUDIT_TRAIL удалён, поскольку записи пишутся прямо в таблицу. В 19c переход в чистый unified mode требует перелинковки бинарного файла oracle с 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":"Добавить политику доступа на уровне строк через VPD (DBMS_RLS)","category":"Безопасность","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 автоматически добавляет предикат WHERE, возвращаемый функцией политики, к каждому SQL против таблицы, поэтому пользователи одной и той же таблицы видят/изменяют только разрешённые им строки. update_check=>TRUE не позволяет пользователю изменить строку на значение, которое он не смог бы увидеть.","when":"В multi-tenant-приложениях, для изоляции данных по регионам/филиалам и чтобы добавить защиту строк без изменения кода приложения.","caution":"Входит в Enterprise Edition, дополнительная лицензия не нужна. CLIENT_IDENTIFIER клиент может задать произвольно; в production используйте контекст приложения, устанавливаемый доверенным пакетом (CREATE CONTEXT ... USING). SYS и пользователи с EXEMPT ACCESS POLICY обходят политику; экспорт Data Pump без этой привилегии молча выгружает неполные данные. Ошибка в функции блокирует любой доступ с ORA-28112. Неверный policy_type (например, STATIC) кэширует предикат и возвращает неверные результаты.","tags":["vpd","virtual private database","dbms_rls","row security","row level security","add_policy","policy function","security"]},{"id":"password-profile-hardening","task":"Создать профиль с политикой паролей и назначить его пользователям","category":"Безопасность","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":"Профиль применяет к группе пользователей правила блокировки после неудачных входов, срока действия пароля, повторного использования, сложности (verify function) и блокировки неактивных учётных записей. PASSWORD_ROLLOVER_TIME позволяет приложениям переключиться без простоя при смене пароля сервисной учётной записи.","when":"Для требований аудита безопасности/CIS benchmark, при создании новых пользователей приложений и чтобы отделить мягкие значения профиля DEFAULT по группам пользователей.","caution":"Дополнительная лицензия не нужна. Лимит FAILED_LOGIN_ATTEMPTS на сервисной учётной записи позволяет одному серверу приложений с неверным паролем заблокировать учётную запись и вызвать простой production. PASSWORD_LIFE_TIME на учётных записях приложений может привести к неожиданному статусу EXPIRED. Функция verify должна существовать под SYS (иначе @?/rdbms/admin/utlpwdmg.sql; этот скрипт также изменяет профиль DEFAULT). Смена профиля не перепроверяет существующие пароли.","note":"INACTIVE_ACCOUNT_TIME добавлен в 12.2; PASSWORD_ROLLOVER_TIME появился в 21c и был бэкпортирован в 19c с 19.12 RU. Сессии, входящие со старым паролем в период rollover, можно отследить по записям аудита.","tags":["profile","password policy","password_verify_function","failed_login_attempts","inactive_account_time","password_rollover_time","account lock","security"]},{"id":"privilege-analysis-capture","task":"Найти неиспользуемые привилегии с помощью privilege analysis","category":"Безопасность","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 во время работы фиксирует, какие системные/объектные привилегии фактически используются в выбранной области (вся база, роль, условие); результат выводит неиспользуемые привилегии, чтобы спланировать REVOKE по принципу наименьших привилегий.","when":"При чистке учётных записей приложений с привилегиями DBA/ANY, при пересмотре привилегий перед аудитом и чтобы выяснить, что действительно нужно коробочному приложению.","caution":"Входит в Enterprise Edition с 2018 года (ранее требовалась лицензия Database Vault); дополнительная лицензия не нужна. Во время сбора есть небольшие накладные расходы. Результат отражает только нагрузку в окне сбора: если задания конца месяца, конца года или аварийные задания не выполнялись, привилегия, показанная как \"неиспользуемая\", может всё же быть нужна, поэтому отзывайте постепенно. Одновременно может быть включён только один сбор; единственное исключение — сбор G_DATABASE можно включить вместе с одним сбором по роли или контексту.","since":"Oracle 12c","tags":["privilege analysis","dbms_privilege_capture","unused privileges","least privilege","dba_unused_privs","privilege cleanup","security"]},{"id":"opatch-inventory-prereq","task":"Просмотреть inventory через OPatch и выполнить проверки перед патчем","category":"Патчинг и жизненный цикл","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":"Выводит бинарные патчи (RU, one-off), применённые к Oracle Home, и перед применением нового патча проверяет, нет ли конфликтов с существующими патчами и достаточно ли места на диске.","when":"Перед ежеквартальным RU/патчем безопасности, когда поддержка запрашивает вывод inventory в SR, и при сравнении уровня патчей двух сред.","caution":"Выполняйте команды от пользователя ОС, владеющего Oracle Home (oracle); opatchauto требует root. Prereq-проверки работают только на чтение и не влияют на базу, но если сообщается о конфликте, не применяйте патч, пока не получите merge/overlay-патч от Oracle Support. Перед применением сделайте бэкап Oracle Home (tar) и inventory.","note":"Для просмотра inventory из SQL можно использовать DBMS_QOPATCH.GET_OPATCH_LSINVENTORY (на установках с OPatch inventory).","tags":["opatch","lsinventory","lspatches","prereq","patch","patch conflict","opatchauto","release update"],"untested":true},{"id":"datapatch-apply-verify","task":"Обновить SQL-уровень через datapatch -verbose после бинарного патча","category":"Патчинг и жизненный цикл","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 применяет SQL/PL-SQL-компоненты (словарь, пакеты) бинарных патчей из Oracle Home к CDB root, PDB$SEED и всем открытым PDB и записывает результат в DBA_REGISTRY_SQLPATCH. Он также откатывает SQL-часть отозванных патчей.","when":"После каждого бинарного RU/RUR/one-off-патча или его отката; для PDB, перенесённых или подключённых через unplug/plug в заново пропатченный home; и для повторного запуска, когда DBA_REGISTRY_SQLPATCH показывает WITH ERRORS.","caution":"Закрытые PDB или PDB в состоянии MOUNTED не патчатся и могут открыться в режиме RESTRICTED; сначала откройте их все. Не запускайте при высокой нагрузке (блокировки словаря, инвалидация пакетов). В RAC запускайте один раз только с одного узла. В Data Guard запускайте datapatch только на primary; изменения доходят до standby через redo (сначала пропатчите бинарные файлы standby).","note":"datapatch из последних RU 19c предлагает -sanity_checks для проверки состояния словаря и среды (невалидные объекты, нарушения PDB, состояние табличных пространств/TEMP) перед патчингом.","tags":["datapatch","sql patch","release update","dba_registry_sqlpatch","post-patch","utlrp","pdb","patching"],"untested":true},{"id":"sqlpatch-registry-check","task":"Проверить историю SQL-патчей, состояние компонентов и невалидные объекты","category":"Патчинг и жизненный цикл","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":"Одним взглядом показывает, действительно ли SQL-уровень обновился после патчинга (словарь соответствует бинарным файлам), какой патч в какой PDB завершился ошибкой, находятся ли компоненты словаря в состоянии VALID, и число невалидных объектов.","when":"Как шаг проверки после каждого datapatch, для фиксации базового состояния перед патчингом или обновлением и при подозрении на несоответствие после подключения PDB.","caution":"Только чтение; лицензия не требуется. SOURCE_VERSION/TARGET_VERSION заполняются в модели RU 18c+. Если STATUS='WITH ERRORS', исправьте ошибку из LOGFILE и запустите datapatch повторно. Некоторые компоненты (например, неиспользуемые XDB/JAVA) могут показывать INVALID/REMOVED; сравните с состоянием до патча.","note":"Чтобы увидеть бинарный inventory из SQL, используйте SELECT XMLTRANSFORM(DBMS_QOPATCH.GET_OPATCH_LIST, DBMS_QOPATCH.GET_OPATCH_XSLT) FROM dual; на установках без OPatch inventory он вызывает ORA-20013.","tags":["dba_registry_sqlpatch","patch history","datapatch","dba_registry","invalid objects","invalid object","patch verification"]},{"id":"scheduler-job-lifecycle","task":"Создать, запустить вручную, отключить и удалить задание планировщика","category":"Планировщик","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":"Определяет запланированное PL/SQL-задание через DBMS_SCHEDULER; задаёт max_run_duration (при превышении генерирует событие) и подробный уровень журналирования, заранее проверяет календарное выражение и позволяет запустить задание вручную, не нарушая расписание.","when":"Чтобы выполнять регулярные работы в базе, такие как обслуживание, ETL и очистка, внутри базы данных вместо cron, с учётом RAC/Data Guard.","caution":"Дополнительная лицензия не нужна. Часовой пояс start_date важен: SYSTIMESTAMP использует TZ сервера базы данных; указывайте явный регион (например, 'Europe/Istanbul'), чтобы избежать сдвига при переходе на летнее/зимнее время. Если JOB_QUEUE_PROCESSES=0, ни одно задание не выполняется. DISABLE с force=>TRUE не останавливает уже выполняющийся запуск, а лишь предотвращает будущие; для прерывания используйте STOP_JOB. Создание задания в другой схеме требует CREATE ANY JOB.","tags":["dbms_scheduler","job","scheduled job","create_job","run_job","repeat_interval","disable job","scheduler"]},{"id":"scheduler-job-history","task":"Мониторинг истории запусков и сбоев заданий планировщика","category":"Планировщик","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":"По представлениям журнала планировщика выводит для каждого запуска время начала, длительность, использование CPU, статус и сообщение об ошибке, а также выполняющиеся задания и события жизненного цикла заданий.","when":"Когда ночное задание не запустилось или выполнялось слишком долго, для отслеживания SLA заданий и при определении правил оповещения о сбоях в системе мониторинга (OEM/Zabbix/Prometheus).","caution":"Только чтение; лицензия не требуется. Срок хранения журнала по умолчанию — 30 дней, а задания с logging_level OFF записей не создают. Столбец ERRORS существует в 12c+; в более старых релизах смотрите ADDITIONAL_INFO. В RAC RUNNING_INSTANCE показывает, на каком узле выполнялось задание.","tags":["scheduler","job history","dba_scheduler_job_run_details","failed job","dba_scheduler_running_jobs","job log","monitoring"]},{"id":"resource-plan-basic","task":"Создать и активировать простой план Resource Manager","category":"Планировщик","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":"Определяет, какая нагрузка выигрывает при конкуренции за CPU: отдаёт группе OLTP 70%, а группе отчётов 20% CPU, ограничивает параллелизм в группе отчётов значением 4 и отменяет вызовы длительностью более 30 минут (CANCEL_SQL). Пользователи автоматически сопоставляются с consumer group.","when":"Для смешанной нагрузки, когда дневные отчёты замедляют OLTP, чтобы ограничить неконтролируемые параллельные запросы и автоматически отменять вышедшие из-под контроля запросы.","caution":"Resource Manager требует Enterprise Edition (в SE2 сильно ограничен); дополнительная лицензия не нужна. Проценты mgmt_p1 действуют только при конкуренции за CPU и в сумме на одном уровне не могут превышать 100%. CANCEL_SQL прерывает выполняющийся оператор с ORA-00040; неверное сопоставление может прервать критичную работу, поэтому тщательно тестируйте пороги. Пока план активен, ограничение CPU проявляется как wait event 'resmgr:cpu quantum'. Для распределения между PDB на уровне CDB используйте 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":"Переключить сегмент WAL","category":"WAL и Checkpoint","command":"SELECT pg_switch_wal();","what":"Закрывает текущий сегмент WAL и переключается на новый; запускает архивирование.","when":"Чтобы получить архивный файл или принудительно вытолкнуть WAL после бэкапа.","caution":"До PostgreSQL 10 это была pg_switch_xlog().","since":"PostgreSQL 10","note":"В PostgreSQL 10 эта функция была переименована в pg_switch_wal(); до этого она называлась pg_switch_xlog() (WAL раньше назывался 'xlog').","tags":["wal","switch","checkpoint","archive"]},{"id":"manual-checkpoint","task":"Выполнить ручной CHECKPOINT","category":"WAL и Checkpoint","command":"CHECKPOINT;","what":"Записывает грязные буферы на диск и создаёт запись checkpoint; сдвигает точку начала восстановления.","when":"Перед бэкапом, в окне обслуживания или для сброса данных на диск перед остановкой сервера.","caution":"Может создавать высокую нагрузку I/O; не запускайте в production в часы пик. Выполнять может только суперпользователь или (PG15+) роль pg_checkpoint.","tags":["checkpoint","wal","io","flush"]},{"id":"wal-status-size","task":"Показать размер WAL и текущий 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":"Возвращает текущий LSN WAL, имя записываемого файла WAL и общий размер каталога pg_wal.","when":"Для мониторинга накопления WAL, использования диска и интервалов между checkpoint.","caution":"pg_ls_waldir и pg_walfile_name доступны в PostgreSQL 10+; для доступа может потребоваться роль pg_monitor.","tags":["wal","lsn","size","monitor"]},{"id":"archive-lag","task":"Показать отставание архивирования (ещё не заархивированный WAL)","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":"Показывает отставание архивирования, сравнивая последний заархивированный файл WAL, текущий записываемый WAL и счётчики ошибок архивирования.","when":"Чтобы отслеживать, не отстаёт ли archive_command, и выявлять сбои архивирования.","caution":"Если failed_count продолжает расти, archive_command завершается ошибкой и pg_wal может заполниться; действуйте немедленно, чтобы не допустить переполнения диска.","tags":["archive","wal","lag","monitor"]},{"id":"create-tablespace","task":"Добавить новое табличное пространство","category":"Хранение","command":"CREATE TABLESPACE ssd_ts OWNER postgres LOCATION '/mnt/ssd/pgdata';","what":"Определяет новое табличное пространство в указанном каталоге; на этот диск можно размещать таблицы и индексы.","when":"Чтобы разнести данные на другой диск (например, быстрый SSD) и разделить I/O.","caution":"Каталог должен уже существовать, быть пустым и принадлежать пользователю postgres. Не разносите данные на другие диски через символические ссылки; используйте собственный механизм табличных пространств PostgreSQL.","tags":["tablespace","storage","disk","create"]},{"id":"move-table-tablespace","task":"Перенести таблицу в другое табличное пространство","category":"Хранение","command":"ALTER TABLE customer SET TABLESPACE ssd_ts;","what":"Физически переносит все данные таблицы в новое табличное пространство (на другой диск).","when":"Чтобы перенести таблицу на более быстрый/отдельный диск.","caution":"На время переноса на таблицу берётся блокировка ACCESS EXCLUSIVE; таблица недоступна до завершения переноса. Для больших таблиц выполняйте в окне обслуживания.","tags":["tablespace","table","move","storage"]},{"id":"move-index-tablespace","task":"Перенести индекс в другое табличное пространство","category":"Хранение","command":"ALTER INDEX customer_email_idx SET TABLESPACE ssd_ts;","what":"Переносит индекс в указанное табличное пространство.","when":"Чтобы разместить индексы на диске, отдельном от данных, и распределить I/O.","caution":"На время переноса берётся блокировка ACCESS EXCLUSIVE; индекс использовать нельзя. ALTER TABLE ... SET TABLESPACE не затрагивает индексы таблицы, поэтому не может перенести их все сразу; переносите каждый индекс отдельно.","tags":["tablespace","index","move","storage"]},{"id":"db-table-size","task":"Запросить размер базы данных и таблиц","category":"Хранение","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":"Выводит 20 самых больших таблиц с их общим размером, включая индексы и TOAST.","when":"Для анализа использования диска и поиска таблиц, занимающих место.","caution":"Размер всей базы данных: SELECT pg_size_pretty(pg_database_size(current_database()));","tags":["size","table","database","storage"]},{"id":"table-bloat","task":"Оценить раздувание (bloat) таблиц","category":"Хранение","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":"Показывает долю раздувания на основе числа мёртвых кортежей и времени последнего autovacuum.","when":"Чтобы определить потребность в VACUUM и понять, успевает ли autovacuum.","caution":"n_dead_tup — это оценка, постоянно обновляемая по счётчикам DML (снижается после VACUUM; ANALYZE лишь корректирует оценку); для точного физического раздувания расширение pgstattuple (pgstattuple('customer')) точнее.","tags":["bloat","dead-tuple","vacuum","storage"]},{"id":"vacuum-analyze-table","task":"Выполнить VACUUM ANALYZE для конкретной таблицы","category":"Обслуживание","command":"VACUUM (ANALYZE, VERBOSE) customer;","what":"Очищает мёртвые кортежи в таблице, делает пространство повторно используемым и обновляет статистику планировщика.","when":"После массовых DELETE/UPDATE или при ухудшении планов запросов.","caution":"VACUUM не возвращает дисковое пространство ОС; для возврата пространства нужен VACUUM FULL, который берёт блокировку ACCESS EXCLUSIVE. Обычный VACUUM не блокирует таблицу.","tags":["vacuum","analyze","maintenance","cleanup"]},{"id":"vacuum-freeze","task":"Выполнить агрессивный VACUUM FREEZE","category":"Обслуживание","command":"VACUUM (FREEZE, VERBOSE, ANALYZE) customer;","what":"Немедленно помечает все подходящие кортежи в таблице как 'frozen'; снижает риск wraparound идентификаторов транзакций.","when":"При появлении предупреждения о wraparound или превентивно на больших таблицах, которые будут архивированы/станут только для чтения.","caution":"Создаёт высокую нагрузку I/O, поскольку сканирует всю таблицу. В аварийной ситуации wraparound база данных фактически может перейти в режим только чтения; заранее отслеживайте datfrozenxid.","tags":["vacuum","freeze","wraparound","maintenance"]},{"id":"reindex-concurrently","task":"Перестроить индекс без блокировки (REINDEX CONCURRENTLY)","category":"Обслуживание","command":"REINDEX INDEX CONCURRENTLY customer_email_idx;","what":"Перестраивает индекс, не блокируя запись; устраняет раздувание индекса.","when":"Когда индекс разросся/повреждён, на работающей системе без окна обслуживания.","caution":"Требует PostgreSQL 12+. При сбое может оставить индекс INVALID с суффиксом _ccnew; проверьте pg_index.indisvalid и при необходимости удалите его через DROP INDEX. Для перестройки всей таблицы: REINDEX TABLE CONCURRENTLY customer;","since":"PostgreSQL 12","note":"REINDEX ... CONCURRENTLY появился в PostgreSQL 12; до этого REINDEX блокировал индекс блокировкой ACCESS EXCLUSIVE (для перестройки без блокировки требовались CREATE INDEX CONCURRENTLY + DROP).","tags":["reindex","concurrently","index","maintenance"]},{"id":"autovacuum-per-table","task":"Настроить autovacuum для конкретной таблицы","category":"Обслуживание","command":"ALTER TABLE customer SET (autovacuum_vacuum_scale_factor = 0.02, autovacuum_vacuum_threshold = 5000, autovacuum_analyze_scale_factor = 0.01);","what":"Снижает пороги срабатывания autovacuum только для этой таблицы, чтобы большие/нагруженные таблицы очищались чаще.","when":"Когда autovacuum не успевает на больших таблицах с интенсивными UPDATE/DELETE.","caution":"Слишком агрессивные настройки могут привести к постоянным запускам autovacuum и нагрузке I/O. Чтобы вернуть настройку: ALTER TABLE customer RESET (autovacuum_vacuum_scale_factor);","tags":["autovacuum","tuning","table","maintenance"]},{"id":"statistics-target","task":"Увеличить statistics target столбца","category":"Обслуживание","command":"ALTER TABLE customer ALTER COLUMN city SET STATISTICS 500; ANALYZE customer;","what":"Увеличивает детализацию гистограммы/выборки, собираемой для конкретного столбца; улучшает оценки планировщика.","when":"Когда на столбцах с неравномерным распределением возникают плохие планы запросов.","caution":"Высокое значение (по умолчанию 100, максимум 10000) увеличивает время ANALYZE и время планирования. Изменение вступает в силу только после ANALYZE.","tags":["statistics","planner","analyze","tuning"]},{"id":"alter-system-reload","task":"Изменить параметр и перечитать конфигурацию (ALTER SYSTEM)","category":"Параметры","command":"ALTER SYSTEM SET work_mem = '64MB'; SELECT pg_reload_conf();","what":"Записывает параметр в postgresql.auto.conf и перечитывает конфигурацию (для параметров, не требующих перезапуска, действует сразу).","when":"Чтобы постоянно изменить параметры, применяемые через reload, такие как work_mem и настройки журналирования.","caution":"Некоторые параметры, такие как shared_buffers и max_connections, вступают в силу только после перезапуска, а не reload; параметры с pg_settings.context = 'postmaster' требуют перезапуска.","tags":["alter-system","reload","parameter","config"]},{"id":"session-set","task":"Изменить параметр для сессии (SET)","category":"Параметры","command":"SET work_mem = '256MB';","what":"Изменяет параметр только для текущего подключения/сессии; при завершении сессии значение сбрасывается.","when":"Когда настройку нужно временно увеличить для одного тяжёлого запроса или пакетного задания.","caution":"Используйте SET LOCAL, чтобы ограничить действие одной транзакцией. Глобальное изменение требует ALTER SYSTEM.","tags":["set","session","parameter","work-mem"]},{"id":"show-setting","task":"Показать текущее значение параметра","category":"Параметры","command":"SELECT name, setting, unit, context, source FROM pg_settings WHERE name = 'shared_buffers';","what":"Показывает действующее значение параметра, единицу измерения, способ применения (context) и источник.","when":"Чтобы узнать текущее значение параметра и требует ли он перезапуска или reload.","caution":"Короткий вариант: SHOW shared_buffers; или SELECT current_setting('shared_buffers'); Если столбец context равен 'postmaster', требуется перезапуск.","tags":["show","pg_settings","parameter","current-setting"]},{"id":"physical-replica-basebackup","task":"Создать физическую реплику (pg_basebackup)","category":"Репликация","command":"pg_basebackup -h primary_host -U replicator -D /var/lib/postgresql/16/main -Fp -Xs -P -R --slot=replica_slot_1 -C","what":"Снимает полный физический бэкап с primary, передаёт WAL потоком (-Xs), записывает standby.signal и primary_conninfo (-R) и создаёт слот репликации (-C).","when":"Для создания нового потокового standby (реплики для чтения или цели failover).","caution":"Целевой каталог (-D) должен быть пустым. На primary pg_hba.conf должен разрешать пользователю replicator доступ 'replication', и должно быть достаточно wal_keep_size / слота. В PostgreSQL 12+ -R создаёт standby.signal вместо recovery.conf.","tags":["replication","basebackup","standby","physical"]},{"id":"promote-replica","task":"Повысить реплику до primary","category":"Репликация","command":"SELECT pg_promote(wait := true, wait_seconds := 60);","what":"Выводит сервер standby из режима восстановления и превращает его в доступный для записи primary.","when":"Чтобы сделать standby новым primary при плановом или аварийном failover.","caution":"Повышение нельзя отменить; сервер больше не сможет следовать за старым primary. Убедитесь, что старый primary действительно остановлен, чтобы не допустить split-brain. Альтернатива: pg_ctl promote.","since":"PostgreSQL 12","note":"SQL-функция pg_promote() появилась в PostgreSQL 12; до этого для повышения standby использовался trigger-файл (promote_trigger_file) или pg_ctl promote.","tags":["replication","promote","failover","standby"]},{"id":"replication-lag","task":"Показать отставание репликации","category":"Репликация","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":"На primary показывает для каждого standby разрыв воспроизведения в байтах относительно текущей позиции WAL primary, а также задержки write/flush/replay.","when":"Для мониторинга отставания потоковой репликации (выполнять на primary).","caution":"Для задержки в секундах на standby: SELECT now() - pg_last_xact_replay_timestamp(); это значение растёт и тогда, когда на primary нет записи, поэтому интерпретируйте его вместе с разрывом LSN. Пустой результат означает, что ни один standby не подключён.","tags":["replication","lag","lsn","monitor"]},{"id":"replication-slot","task":"Создать и удалить слот репликации","category":"Репликация","command":"SELECT pg_create_physical_replication_slot('replica_slot_1'); -- To drop: SELECT pg_drop_replication_slot('replica_slot_1');","what":"Создаёт физический слот, гарантирующий, что primary не удалит нужный WAL, пока standby его не подтвердит; вторая команда удаляет слот.","when":"Чтобы standby не терял WAL при кратковременных отключениях.","caution":"Неиспользуемый/заброшенный слот приводит к накоплению WAL и переполнению диска. Следите за состоянием слотов: SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained FROM pg_replication_slots; Активный слот удалить нельзя.","tags":["replication","slot","wal","cleanup"]},{"id":"logical-replication","task":"Настроить публикацию и подписку логической репликации","category":"Репликация","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":"Запускает логическую репликацию на уровне таблиц: создаёт на источнике публикацию для определённых таблиц, а на целевой стороне — подписку, подключающуюся к ней.","when":"Для обновления версии, репликации выбранных таблиц на другой сервер или одностороннего распространения данных.","caution":"Требует PostgreSQL 10+. Схема целевых таблиц должна уже существовать; подписка автоматически выполняет начальный COPY. wal_level должен быть logical (требуется перезапуск). Изменения DDL и значения последовательностей не реплицируются; применяйте изменения схемы на обеих сторонах отдельно и синхронизируйте последовательности через setval при переключении.","since":"PostgreSQL 10","note":"Встроенная логическая репликация (CREATE PUBLICATION / CREATE SUBSCRIPTION) появилась в PostgreSQL 10; до этого для той же задачи требовались внешние расширения, такие как pglogical.","tags":["logical","replication","publication","subscription"]},{"id":"terminate-backend","task":"Завершить backend (pg_terminate_backend)","category":"Сессии","command":"SELECT pg_terminate_backend(12345);","what":"Принудительно завершает backend-процесс с указанным PID; разрывает подключение и прерывает выполняющуюся транзакцию.","when":"Чтобы закрыть зависшее подключение или сессию, удерживающую блокировку.","caution":"Выполняющаяся транзакция откатывается; незафиксированная работа теряется. Сначала попробуйте более мягкую pg_cancel_backend. Проверьте PID в pg_stat_activity.","tags":["terminate","backend","session","kill"]},{"id":"cancel-backend","task":"Отменить выполняющийся запрос (pg_cancel_backend)","category":"Сессии","command":"SELECT pg_cancel_backend(12345);","what":"Отменяет запрос, выполняющийся на указанном PID; подключение остаётся открытым.","when":"Чтобы остановить долгий запрос, не разрывая подключение.","caution":"Отменяет только активный запрос; сессию в состоянии idle in transaction она не закрывает — для этого нужна pg_terminate_backend.","tags":["cancel","backend","query","session"]},{"id":"active-blocking-queries","task":"Показать активные и блокирующие запросы","category":"Сессии","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":"Выводит все не простаивающие backend, PID, которые их блокируют, и длительность их выполнения.","when":"Для диагностики проблем с блокировками и долгих запросов.","caution":"pg_blocking_pids доступна в PostgreSQL 9.6+. Если массив blocking_pids не пуст, эти PID удерживают блокировку; при необходимости отмените/завершите этот PID.","since":"PostgreSQL 9.6","note":"Функция pg_blocking_pids() появилась в PostgreSQL 9.6; до этого для поиска блокирующих процессов требовалось вручную соединять pg_locks.","tags":["activity","blocking","lock","session"]},{"id":"create-extension","task":"Добавить расширение","category":"Расширения","command":"CREATE EXTENSION IF NOT EXISTS pg_stat_statements;","what":"Устанавливает указанное расширение в текущую базу данных (добавляет функции, представления, типы и т. д.).","when":"Когда нужны дополнительные возможности, такие как pg_stat_statements, pgcrypto или postgis.","caution":"Расширения, требующие shared_preload_libraries, такие как pg_stat_statements, сначала добавьте в postgresql.conf и перезапустите сервер. Файлы расширения (пакет contrib) должны быть установлены на сервере.","tags":["extension","create","pg_stat_statements","plugin"]},{"id":"list-extensions","task":"Показать установленные расширения","category":"Расширения","command":"SELECT extname AS extension, extversion AS version FROM pg_extension ORDER BY extname;","what":"Выводит все расширения, установленные в текущей базе данных, и их версии.","when":"Чтобы увидеть, какие расширения установлены и нужно ли их обновление.","caution":"Доступные для установки (но ещё не установленные) расширения: SELECT * FROM pg_available_extensions; Обновление: 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":"Показать состояние, топологию и историю кластера Patroni","category":"Patroni","command":"# Members, roles, state, timeline and lag (MB):\npatronictl -c /etc/patroni/patroni.yml list <cluster>\n# Extended info (pending restart, tags) + timestamp, refresh every 5 s:\npatronictl -c /etc/patroni/patroni.yml list <cluster> -e -t -w 5\n# Leader -> replica tree (including cascading replicas):\npatronictl -c /etc/patroni/patroni.yml topology <cluster>\n# Past failovers/switchovers (timeline, LSN, reason, time):\npatronictl -c /etc/patroni/patroni.yml history <cluster>","what":"Показывает представление Patroni о кластере из DCS: роли leader/replica, состояние участников (running/streaming), timeline, отставание репликации и ожидающие перезапуски; history выводит смены timeline с LSN, на котором они произошли.","when":"До и после каждого switchover/failover/обслуживания и как первый шаг диагностики при срабатывании алерта.","caution":"Вывод отражает то, что хранится в DCS; если DCS недоступен, команда завершится ошибкой или может показать устаревшие данные. Не начинайте switchover, пока не убедитесь, что все участники на одном timeline, а Lag in MB ниже maximum_lag_on_failover.","untested":true,"tags":["patroni","cluster status","topology","history","lag"]},{"id":"patroni-switchover","task":"Выполнить плановый switchover через 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":"Контролируемо понижает исправного leader и повышает выбранную реплику (--candidate); бывший leader автоматически подключается как реплика нового leader. --scheduled откладывает операцию до указанного времени.","when":"Чтобы перенести primary на другой узел перед обслуживанием (патчинг ОС, замена оборудования, обновление минорной версии) или восстановить исходные роли после failover.","caution":"При смене leader все подключения разрываются; приложение должно уметь переподключаться. Patroni отклоняет switchover, если отставание кандидата превышает maximum_lag_on_failover. При включённом synchronous_mode кандидат должен быть синхронным standby.","note":"В старых релизах Patroni опция --leader называлась --master; в Patroni 4 --master удалена. --primary эквивалентна --leader.","untested":true,"tags":["patroni","switchover","planned switchover","maintenance","leader"]},{"id":"patroni-failover","task":"Выполнить ручной failover через 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":"Повышает указанную реплику независимо от состояния leader; предназначено для случаев, когда автоматический failover не произошёл или выбрал не тот узел.","when":"Когда leader недоступен/неисправен, а автоматический failover не сработал (например, в режиме pause), или когда нужно принудительно перейти на определённый узел.","caution":"При асинхронной репликации транзакции, ещё не полученные кандидатом, теряются (RPO > 0). Если leader исправен, используйте switchover вместо failover. Когда старый leader вернётся, ему может потребоваться pg_rewind (если включён use_pg_rewind) или reinit.","note":"В Patroni 3.2+ --candidate для failover обязателен; опция --leader/--primary у failover объявлена устаревшей в 3.2 и удалена в Patroni 4.0. Если нужно указать leader, используйте switchover.","untested":true,"tags":["patroni","failover","manual failover","promote","rpo"]},{"id":"patroni-reinit-replica","task":"Пересоздать сломанную реплику через 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":"Удаляет каталог данных указанной реплики и пересоздаёт её с нуля согласно create_replica_methods Patroni (восстановление pgBackRest или pg_basebackup).","when":"Когда реплика не может вернуться в репликацию из-за несовпадения timeline, неудачного pg_rewind, отсутствующего WAL или повреждения данных.","caution":"Все локальные данные участника удаляются; дважды проверьте, что команда нацелена на нужного участника (Patroni отказывается выполнять reinit на leader). На больших базах это может занять часы и создаёт нагрузку I/O/сети на leader; всё это время в кластере на одну реплику меньше. --from-leader есть только в последних релизах Patroni 4.x (проверьте через patronictl reinit --help); он пропускает create_replica_methods и снимает pg_basebackup с leader.","untested":true,"tags":["patroni","reinit","rebuild replica","rebuild","basebackup"]},{"id":"patroni-edit-config","task":"Изменить параметры всего кластера в Patroni (edit-config) и применить ожидающие перезапуски","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":"Записывает параметры PostgreSQL и Patroni в динамическую конфигурацию в DCS; Patroni применяет изменение ко всем участникам, сразу активирует параметры, применяемые через reload, и помечает требующие перезапуска как 'Pending restart'.","when":"Чтобы согласованно изменить на каждом узле кластера под управлением Patroni параметры, такие как max_connections, shared_buffers или work_mem.","caution":"Под Patroni не редактируйте postgresql.conf вручную и не используйте ALTER SYSTEM: Patroni перезаписывает postgresql.conf, а postgresql.auto.conf переопределяет настройки Patroni, что приводит к расхождению между узлами. max_connections, max_worker_processes, max_wal_senders, max_prepared_transactions и max_locks_per_transaction на standby не могут быть ниже, чем на primary; планируйте порядок перезапуска соответственно.","untested":true,"tags":["patroni","edit-config","parameter","pending restart","dcs"]},{"id":"patroni-pause-resume","task":"Перевести Patroni в режим обслуживания и обратно (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":"Переводит кластер в режим обслуживания: Patroni не выполняет автоматический failover и не перезапускает остановленный PostgreSQL; resume возвращает обычное управление.","when":"Во время обслуживания DCS (etcd/Consul), обновления Patroni или когда PostgreSQL нужно управлять вручную без вмешательства Patroni.","caution":"Если primary упадёт в режиме паузы, автоматического failover не будет и простой продолжится. Не забудьте выполнить resume после обслуживания; добавьте проверку 'Maintenance mode' в алертинг. Ручной запуск PostgreSQL как primary в режиме паузы может вызвать split-brain.","untested":true,"tags":["patroni","pause","resume","maintenance mode","maintenance"]},{"id":"patroni-dcs-health","task":"Проверить работоспособность DCS Patroni (etcd/Consul) и REST API","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":"Проверяет кворум, leader и ёмкость DCS, где Patroni держит leader lock, а также собственное представление каждого участника о своей роли через REST API Patroni. Endpoint /primary лежит в основе проверок работоспособности балансировщика нагрузки (HAProxy).","when":"Когда Patroni пишет в журнал 'DCS is not accessible', после неожиданного demote/failover и как регулярная проверка работоспособности.","caution":"Если DCS теряет кворум, Patroni не может продлить leader lock и понижает primary до режима только чтения (если не включён failsafe_mode, Patroni 3.0+); поэтому сбой etcd — это простой записи в базу данных. Когда размер БД etcd достигает квоты (по умолчанию 2 GB), алерт NOSPACE останавливает запись. Запускайте DCS минимум на 3 узлах на отдельных дисках.","untested":true,"tags":["patroni","etcd","consul","dcs health","rest api"]},{"id":"pgbackrest-archive-config","task":"Настроить архивирование WAL для pgBackRest (archive-push/archive-get)","category":"Бэкап и 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":"Заставляет PostgreSQL отправлять каждый завершённый сегмент WAL в репозиторий pgBackRest (archive-push) и получать его оттуда при восстановлении (archive-get); archive-async делает отправку параллельной и асинхронной через spool-каталог.","when":"Как первый шаг настройки физических бэкапов и PITR с pgBackRest, перед stanza-create.","caution":"Пока archive_command завершается ошибкой, WAL накапливается в pg_wal, и при заполнении диска primary останавливается; следите за pg_stat_archiver.failed_count. С Patroni задавайте эти параметры через patroni.yml / edit-config, а не в postgresql.conf. Архивирование из двух разных кластеров в одну stanza повреждает репозиторий.","untested":true,"tags":["pgbackrest","archiving","archive_command","wal archive","configuration"]},{"id":"pgbackrest-stanza-create-check","task":"Создать stanza pgBackRest и проверить архивирование (stanza-create/check)","category":"Бэкап и 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 создаёт в репозитории метаданные кластера (system identifier, версия); check проверяет подключение к PostgreSQL, archive_command и поступление WAL в репозиторий с реальным переключением WAL.","when":"При первоначальной настройке, после изменения archive_command или репозитория и как проверка работоспособности в мониторинге/cron.","caution":"Выполняйте команды от postgres (или владельца репозитория); запуск от root нарушает владельца файлов. После обновления мажорной версии требуется stanza-upgrade, а не stanza-create. Если check завершается ошибкой, PITR может оказаться невозможным, даже если бэкапы проходят успешно.","untested":true,"tags":["pgbackrest","stanza","stanza-create","check","archive verification"]},{"id":"pgbackrest-backup-full-diff-incr","task":"Снять полный, дифференциальный и инкрементальный бэкапы через pgBackRest","category":"Бэкап и 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":"Полный бэкап копирует весь кластер, diff — файлы, изменённые после последнего full, incr — файлы, изменённые после последнего бэкапа любого типа; в конце проверяется, что нужный WAL заархивирован, и автоматически выполняется expire согласно политике хранения.","when":"В регулярном расписании бэкапов: обычно еженедельный full + ежедневный diff/incr, на больших кластерах — более частые incr.","caution":"Без предварительного full запросы diff/incr автоматически повышаются до full. Более длинная цепочка incr увеличивает время восстановления и зависимости; потеря одного бэкапа в цепочке делает последующие непригодными. Чтобы разгрузить primary, снимайте бэкап со standby с backup-standby=y (primary всё равно нужен для координации).","untested":true,"tags":["pgbackrest","backup","full","differential","incremental"]},{"id":"pgbackrest-info","task":"Показать каталог бэкапов pgBackRest и диапазон архива WAL (info)","category":"Бэкап и 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":"Выводит состояние stanza, метку каждого бэкапа, тип, время начала/окончания, размер, бэкап, от которого он зависит, и диапазон min/max архива WAL в репозитории.","when":"Перед восстановлением/PITR — чтобы убедиться, что целевое время покрыто, для контроля регулярности бэкапов и проверки политики хранения.","caution":"info показывает, что бэкап существует, а не что он восстанавливается; проверяйте регулярными тестовыми восстановлениями и pgbackrest verify. Если в диапазоне min/max WAL есть разрыв, PITR не сможет его пересечь.","untested":true,"tags":["pgbackrest","info","backup list","catalog","monitoring"]},{"id":"pgbackrest-restore-latest","task":"Восстановить последний бэкап через pgBackRest","category":"Бэкап и 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":"Восстанавливает последний бэкап и записывает recovery.signal и restore_command; при запуске PostgreSQL воспроизводит весь заархивированный WAL до последней согласованной точки и выполняет promote на новом timeline.","when":"Когда каталог данных primary утерян/повреждён или при регулярной проверке того, что бэкапы восстанавливаются.","caution":"Если восстановленный и старый кластеры одновременно архивируют в одну stanza, репозиторий повреждается; для тестовых восстановлений используйте --archive-mode=off. Под Patroni сначала поставьте кластер на паузу или остановите Patroni, иначе Patroni вмешается. Если существуют табличные пространства или пути вне PGDATA, проверьте опции --tablespace-map и link.","untested":true,"tags":["pgbackrest","restore","restore","recovery","disaster recovery"]},{"id":"pgbackrest-pitr-restore","task":"Восстановить на момент времени через pgBackRest (PITR, --type=time)","category":"Бэкап и 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":"Восстанавливает бэкап, завершившийся до целевого времени, и воспроизводит WAL только до цели; target-action=pause останавливается на цели, чтобы можно было проверить данные, а pg_wal_replay_resume() выполняет promote.","when":"Чтобы вернуться к моменту непосредственно перед логической ошибкой, такой как случайный DELETE/DROP/UPDATE.","caution":"Все изменения после цели в этом кластере теряются; по возможности выполняйте PITR на отдельном сервере и переносите обратно только затронутые данные через pg_dump. Указывайте часовой пояс в цели явно. pgBackRest 2.24+ автоматически выбирает подходящий бэкап до цели; в более старых релизах укажите через --set бэкап, завершившийся до цели. Если цель раньше времени окончания бэкапа, восстановление не сможет достичь согласованности.","untested":true,"tags":["pgbackrest","pitr","point in time recovery","point in time","recovery"]},{"id":"pgbackrest-delta-restore-standby","task":"Быстро пересоздать standby через delta restore pgBackRest","category":"Бэкап и 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 сравнивает файлы в существующем PGDATA с бэкапом (размер/время изменения и контрольная сумма) и копирует только отличающиеся; --type=standby записывает standby.signal, а recovery-option добавляет primary_conninfo. Standby сначала догоняет по архиву, затем переключается на потоковую репликацию.","when":"Чтобы без полного копирования снова сделать репликой отстающий standby, standby с разошедшимся timeline или старый primary, для которого pg_rewind не сработал.","caution":"Delta заменяет/удаляет отличающиеся или лишние файлы в целевом каталоге файлами из бэкапа; запуск на неверном сервере уничтожит данные этого сервера. pgBackRest отказывается восстанавливать поверх работающего PostgreSQL, но сами убедитесь, что сервис остановлен. Храните пароль в .pgpass, а не в primary_conninfo.","untested":true,"tags":["pgbackrest","delta restore","rebuild standby","rebuild","replica"]},{"id":"pgbackrest-retention-expire","task":"Настроить политику хранения pgBackRest и выполнить expire","category":"Бэкап и 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":"Определяет, сколько полных/дифференциальных бэкапов (count) или за сколько дней полных бэкапов (time) хранить; expire удаляет бэкапы вне политики, зависящие от них бэкапы diff/incr и более не нужный архив WAL.","when":"Чтобы контролировать размер репозитория и гарантировать требуемое окно RPO/PITR (например, возможность вернуться на 14 дней назад).","caution":"Окно PITR ограничено самым старым сохранённым полным бэкапом; при еженедельных full и retention-full=2 можно вернуться назад максимум примерно на 2 недели. repo1-retention-archive по умолчанию следует хранению full; если задать меньшее значение, старые бэкапы будут пригодны только до точки согласованности. Expire нельзя отменить.","untested":true,"tags":["pgbackrest","retention","retention period","expire","backup cleanup"]},{"id":"standby-manual-config","task":"Настроить standby вручную (standby.signal + primary_conninfo)","category":"Репликация","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":"Файл standby.signal заставляет сервер запуститься как standby; primary_conninfo задаёт потоковое подключение, а primary_slot_name — используемый слот репликации. В PG13+ primary_conninfo и primary_slot_name можно изменить через reload.","when":"Чтобы превратить в standby каталог данных, подготовленный без pg_basebackup -R (pgBackRest, snapshot, rsync), или перенаправить существующий standby на новый primary.","caution":"Слот должен быть заранее создан на primary (pg_create_physical_replication_slot), иначе walreceiver будет постоянно завершаться ошибкой. Пароль храните в ~/.pgpass, а не в conninfo. application_name должен точно совпадать с именем в synchronous_standby_names. Если забыть standby.signal, сервер запустится как primary и перейдёт на отдельный timeline.","since":"PostgreSQL 12","note":"До PostgreSQL 12 эти настройки находились в recovery.conf (standby_mode = on); в PG12 они перенесены в standby.signal и postgresql.conf. До PG13 изменение primary_conninfo требовало перезапуска.","tags":["standby","standby.signal","primary_conninfo","replica setup","streaming"]},{"id":"standby-wal-receiver-lag","task":"Мониторинг задержки получения и воспроизведения WAL на standby (pg_stat_wal_receiver)","category":"Репликация","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":"Показывает, сколько WAL standby получил и сколько применил (отставание воспроизведения), возраст последней воспроизведённой транзакции, приостановлено ли воспроизведение и состояние подключения walreceiver (к какому primary, слот, timeline, время последнего сообщения).","when":"Для диагностики задержки, когда primary недоступен, или на каскадных standby; при алерте 'standby behind' — чтобы отделить сетевую проблему (receive) от проблемы воспроизведения (I/O, конфликт).","caution":"Если pg_stat_wal_receiver не возвращает строк, standby не использует потоковую репликацию (получает данные из архива или отключён). last_replay_age растёт, даже когда на primary нет записи; интерпретируйте его вместе с разницей receive_lsn/replay_lsn. Если отставание воспроизведения растёт, а получение продвигается, на standby есть долгие запросы (max_standby_streaming_delay) или узкое место I/O.","since":"PostgreSQL 14","note":"pg_get_wal_replay_pause_state() добавлена в PostgreSQL 14; до этого существовала только pg_is_wal_replay_paused(). Остальная часть запроса работает в PG10+.","tags":["standby","replication lag","wal receiver","replay","lag"]},{"id":"pg-rewind-old-primary","task":"Вернуть старый primary как реплику после failover через pg_rewind","category":"Репликация","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":"Приводит каталог данных старого primary в соответствие с новым primary, копируя с нового primary блоки, изменённые после точки расхождения timeline; за минуты превращает его в реплику без полного basebackup.","when":"Когда старый primary возвращается после failover/promote и отстаёт от нового timeline с разошедшимися изменениями.","caution":"Транзакции, оставшиеся на старом primary после failover и так и не дошедшие до нового primary, удаляются безвозвратно. pg_rewind также копирует с источника файлы конфигурации (postgresql.conf, postgresql.auto.conf, pg_hba.conf); перед запуском исправьте настройки порта и слота. Если WAL, нужный с точки расхождения, на источнике уже отсутствует, получите его из архива с -c (--restore-target-wal). Для подключения к источнику нужен суперпользователь или роль с привилегиями на функции, перечисленные в документации. В Patroni это выполняется автоматически при use_pg_rewind: true.","since":"PostgreSQL 13","note":"-R/--write-recovery-conf и -c/--restore-target-wal добавлены в PostgreSQL 13; в PG13+ pg_rewind сам выполняет crash recovery на некорректно остановленной цели (отключается через --no-ensure-shutdown). pg_rewind существует с 9.5.","tags":["pg_rewind","after failover","old primary","timeline","rejoin"]},{"id":"sync-standby-quorum","task":"Настроить синхронную репликацию с кворумом (ANY) или по приоритету (FIRST)","category":"Репликация","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":"Определяет, какие standby должны подтвердить commit до его завершения: ANY N основан на кворуме (sync_state = quorum), FIRST N делает синхронными (sync) первые N подключённых standby в порядке списка, а остальные — ожидающими (potential). Уровень подтверждения задаётся synchronous_commit (remote_write, on, remote_apply).","when":"В системах, требующих отсутствия потерь данных при failover (RPO = 0); при нескольких standby предпочтителен ANY, чтобы отказ одного standby не останавливал запись.","caution":"Если требуемое число синхронных standby не подключено, commit зависает без ошибки, и приложение замирает. Имена в списке должны совпадать с application_name standby. В экстренной ситуации отдельная сессия может обойти это через SET synchronous_commit = local. Под Patroni управляйте этим через synchronous_mode, а не вручную.","since":"PostgreSQL 10","note":"Синтаксис FIRST добавлен в PostgreSQL 9.6, а ANY (quorum commit) — в PostgreSQL 10; форма 'N (a, b)' без ключевого слова означает FIRST.","tags":["synchronous replication","synchronous_standby_names","quorum","rpo","sync"]},{"id":"slot-retained-wal-monitor","task":"Мониторинг WAL, удерживаемого слотами репликации, и безопасное удаление заброшенного слота","category":"Репликация","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":"Показывает, сколько WAL удерживает каждый слот на primary, wal_status (reserved/extended/unreserved/lost), оставшийся безопасный запас в пределах max_slot_wal_keep_size (safe_wal_size) и значения xmin/catalog_xmin, сдерживающие VACUUM; второй запрос удаляет слот, только если он не используется.","when":"Когда растёт каталог pg_wal, при алерте по диску и в регулярном мониторинге; для очистки слотов после окончательного удаления standby или подписчика.","caution":"Неактивный слот удерживает WAL без ограничений и может заполнить диск и остановить primary; слот, удерживающий xmin/catalog_xmin, также блокирует очистку мёртвых кортежей и создаёт риск раздувания/wraparound. После удаления слота его standby/подписчик не сможет продолжить с того места, где остановился (потребуется пересоздание); перед удалением проверьте его владельца. Выполняйте на primary (pg_current_wal_lsn() на standby завершается ошибкой).","since":"PostgreSQL 13","note":"wal_status и safe_wal_size добавлены в PostgreSQL 13. В PG16 для логических слотов добавлен conflicting, в PG17 — inactive_since; inactive_since — самый прямой признак заброшенного слота.","tags":["replication slot","wal accumulation","disk full","drop slot","retained wal"]},{"id":"slot-max-wal-keep-size","task":"Ограничить WAL, который могут удерживать слоты (max_slot_wal_keep_size)","category":"Репликация","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":"Задаёт верхнюю границу WAL, который слоты репликации могут хранить в pg_wal (по умолчанию -1 = без ограничений); при превышении checkpoint удаляет старый WAL, и слот получает wal_status = 'lost'. Параметр вступает в силу через reload.","when":"Как защита в каждом кластере, использующем слоты, от риска того, что отключившийся standby или подписчик заполнит диск primary и остановит всю систему.","caution":"Лимит отдаёт приоритет работоспособности primary за счёт владельца слота: standby со слотом 'lost' нужно пересоздать, а логическую подписку — создать заново. Выберите значение меньше свободного места на диске pg_wal, но больше объёма WAL, генерируемого за самый долгий ожидаемый простой. Если wal_status равен 'unreserved', слот вот-вот будет потерян; поднимите алерт.","since":"PostgreSQL 13","note":"max_slot_wal_keep_size добавлен в PostgreSQL 13; до этого не было способа ограничить WAL, удерживаемый слотами, и единственной защитой были мониторинг и ручное удаление слотов.","tags":["max_slot_wal_keep_size","slot limit","wal accumulation","disk protection","replication slot"]},{"id":"logical-add-table-refresh","task":"Добавить таблицу в публикацию и обновить подписку (REFRESH PUBLICATION)","category":"Логическая репликация","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":"Добавляет новую таблицу в публикацию на издателе; на подписчике REFRESH PUBLICATION включает новые таблицы в подписку и запускает начальное копирование (tablesync) с copy_data = true. srsubstate: i = init, d = копирование данных, f = копирование завершено, s = синхронизировано, r = ready.","when":"Когда в работающую логическую репликацию добавляется новая таблица или таблица удаляется из публикации.","caution":"Даже с публикациями FOR ALL TABLES или TABLES IN SCHEMA подписчик не подхватывает новую таблицу, пока не выполнен REFRESH. REFRESH завершается ошибкой, если таблицы нет на подписчике; DDL не реплицируется. Если у опубликованной таблицы нет PRIMARY KEY или REPLICA IDENTITY, UPDATE/DELETE на издателе завершаются ошибкой. Начальное копирование большой таблицы надолго удерживает слот и снимок на издателе.","since":"PostgreSQL 10","note":"ADD TABLES IN SCHEMA добавлен в PostgreSQL 15 (в PG15 также добавлены фильтры строк через WHERE и списки столбцов).","tags":["logical replication","publication","add table","refresh publication","subscription"]},{"id":"logical-subscription-monitor","task":"Мониторинг логической репликации (pg_stat_subscription, ошибки apply, отставание слота)","category":"Логическая репликация","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":"На подписчике показывает рабочие процессы apply/tablesync, полученный и подтверждённый LSN, время последнего сообщения и накопленное число ошибок apply/sync; на издателе показывает неподтверждённый (confirmed_flush) WAL логического слота и общий удерживаемый им WAL.","when":"При настройке логической репликации, чтобы убедиться, что отставание приближается к нулю при переключении в ходе обновления/миграции, и для алертинга по ошибкам.","caution":"Если pid равен NULL, рабочий процесс apply не запущен (подписка отключена или постоянно падает и перезапускается); проверьте журнал сервера. Рост apply_error_count указывает на конфликт. Остановившаяся подписка накапливает WAL и catalog_xmin на издателе; следите за диском издателя и раздуванием каталога.","since":"PostgreSQL 15","note":"pg_stat_subscription_stats добавлено в PostgreSQL 15, pg_stat_subscription.leader_pid — в PG16 (parallel apply); в PG17 добавлен столбец worker_type. Базовые столбцы pg_stat_subscription существуют с PG10.","tags":["logical replication","subscription monitoring","pg_stat_subscription","apply error","lag"]},{"id":"logical-conflict-skip","task":"Разрешить конфликт логической репликации (disable_on_error, SKIP)","category":"Логическая репликация","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":"Когда ошибка, например duplicate key, отсутствующая строка или проблема с правами, останавливает apply на подписчике: disable_on_error автоматически отключает подписку; либо исправьте конфликтующие данные и выполните ENABLE, либо пропустите через SKIP всю удалённую транзакцию, определённую по LSN 'finished at' в журнале.","when":"Когда рабочий процесс apply падает и постоянно перезапускается из-за прямой записи на подписчике, неполного начального копирования или двунаправленной записи.","caution":"SKIP также отбрасывает неконфликтующие изменения этой транзакции, и стороны расходятся навсегда; предпочтительнее исправить данные, а после SKIP сравните таблицу. Пока конфликт не разрешён, слот на издателе продолжает накапливать WAL. Запретите запись приложения в таблицы подписчика с помощью привилегий.","since":"PostgreSQL 15","note":"SKIP и disable_on_error добавлены в PostgreSQL 15; до этого то же делалось сдвигом origin на подписчике через pg_replication_origin_advance() (более рискованно).","tags":["logical replication","conflict","conflict","skip","duplicate key"]},{"id":"logical-sync-sequences","task":"Синхронизировать значения последовательностей на подписчике при логической репликации","category":"Логическая репликация","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":"Поскольку логическая репликация не переносит значения последовательностей (включая identity), скрипт читает последнее значение каждой последовательности на издателе, генерирует и сразу применяет команды setval, устанавливающие то же значение на подписчике.","when":"При переключении в ходе обновления/миграции через логическую репликацию, непосредственно перед перенаправлением приложения на подписчика.","caution":"Если запустить, пока издатель ещё принимает запись, значения сразу устаревают, и после переключения возникают ошибки duplicate key; запускайте после остановки записи или добавьте запас. DDL тоже не реплицируется: применяйте изменения схемы (ALTER TABLE, новые индексы) на обеих сторонах отдельно, сначала на подписчике. pg_sequences возвращает NULL в last_value для последовательностей, которые вы не можете читать; выполняйте команду от владельца или суперпользователя.","since":"PostgreSQL 10","tags":["logical replication","sequence","setval","ddl not replicated","cutover"]},{"id":"logical-orphan-slot-cleanup","task":"Удалить подписку и очистить осиротевший логический слот на издателе","category":"Логическая репликация","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":"Удаляет подписку, не трогая удалённый слот (slot_name = NONE); затем находит и удаляет на издателе логические слоты, у которых больше нет потребителя, а также оставшиеся слоты tablesync.","when":"Когда подписку нужно удалить при недоступном издателе, после завершения миграции или когда логический слот с active = false накапливает WAL на издателе.","caution":"Осиротевший логический слот удерживает и WAL, и catalog_xmin: диск заполняется, а системные каталоги раздуваются. Однако после удаления слота эта подписка не сможет продолжить работу; перед удалением убедитесь, что этот слот не использует ни один подписчик (в том числе на другом сервере). Активный слот удалить нельзя; сначала нужно завершить подключённый walsender.","since":"PostgreSQL 10","tags":["logical replication","slot cleanup","drop subscription","orphaned slot","catalog_xmin"]},{"id":"pg-upgrade-link-runbook","task":"Обновление мажорной версии через pg_upgrade --link (сквозной runbook)","category":"Обновление","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":"Сначала проверяет совместимость через --check, затем выполняет мажорное обновление за минуты независимо от объёма данных, создавая жёсткие ссылки на файлы данных старого кластера в новой версии вместо их копирования; после этого поэтапно собирает статистику и удаляет старый кластер.","when":"Чтобы перевести большой кластер на месте на новую мажорную версию с приемлемо коротким простоем (минуты).","caution":"С --link после запуска нового кластера старый кластер больше нельзя безопасно использовать; единственный путь назад — бэкап, снятый до обновления. Старый и новый каталоги данных должны находиться в одной файловой системе. Пока analyze не завершён, планы в новом кластере могут быть плохими. Если используете pgBackRest, сразу выполните stanza-upgrade и снимите новый полный бэкап; бэкапы старой версии нельзя восстановить в новый кластер.","note":"Физические standby не обновляются через pg_upgrade: после обновления primary обновите их методом rsync --hard-links --size-only из документации или пересоздайте. Логические слоты до PG17 не переносятся (переносятся, если старый кластер PG17+); статистика планировщика до PG18 не переносится.","tags":["pg_upgrade","major upgrade","version upgrade","link","check"]},{"id":"logical-upgrade-near-zero-downtime","task":"Обновление мажорной версии почти без простоя через логическую репликацию","category":"Обновление","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":"Создаёт пустой кластер на новой версии, переносит схему и непрерывно передаёт данные из старой версии через логическую репликацию; короткий простой вызывает только финальный шаг переключения (остановка записи, синхронизация последовательностей, перенаправление). Вторая репликация в обратном направлении позволяет сохранить путь отката.","when":"Когда простой, требуемый pg_upgrade, неприемлем, при одновременном переходе на новое оборудование/ОС или при пропуске нескольких мажорных версий.","caution":"DDL, последовательности, большие объекты и содержимое материализованных представлений не реплицируются; замораживайте схему на время миграции. На таблицах без первичного ключа/replica identity UPDATE/DELETE на издателе завершаются ошибкой. На больших базах начальное копирование занимает много времени и удерживает WAL и catalog_xmin на старом сервере. Каждой базе данных нужна своя публикация/подписка. Перед переключением сравните число строк/контрольные суммы.","since":"PostgreSQL 10","note":"pg_createsubscriber, добавленный в PostgreSQL 17, превращает физический standby в логического подписчика без начального копирования, устраняя время начального копирования на больших базах.","tags":["upgrade","logical replication","zero downtime migration","major upgrade","cutover"]},{"id":"pgbouncer-show-pools-stats","task":"Показать пулы, статистику и подключения PgBouncer (SHOW POOLS/STATS/CLIENTS/SERVERS)","category":"PgBouncer","command":"# Connect to the PgBouncer admin console (user must be in admin_users/stats_users):\npsql -h 127.0.0.1 -p 6432 -U pgbouncer -d pgbouncer -X\nSHOW POOLS;    -- if cl_waiting > 0 and maxwait grows, the pool is saturated\nSHOW STATS;    -- per-database total/average query, transaction and wait times\nSHOW CLIENTS;  -- client connections (state, source address, linked server connection)\nSHOW SERVERS;  -- server connections opened to PostgreSQL\nSHOW CONFIG;","what":"Для каждого пула базы данных/пользователя показывает активных и ожидающих клиентов (cl_active, cl_waiting), активные/простаивающие серверные подключения (sv_active, sv_idle), наибольшее время ожидания (maxwait) и счётчики запросов/транзакций.","when":"Когда приложение сообщает о сбоях подключения/тайм-аутах, для определения размера пула и чтобы увидеть активные подключения перед обслуживанием.","caution":"Консоль администратора не принимает SQL; она выполняет только команды PgBouncer. Если cl_waiting остаётся высоким, сначала проверьте серверную сторону на долгие транзакции или блокировки; слепое увеличение default_pool_size подталкивает PostgreSQL к лимиту max_connections и нехватке памяти. Значения SHOW STATS накопительные; средние относятся к интервалу stats_period.","untested":true,"tags":["pgbouncer","show pools","connection pooling","statistics","connection pool"]},{"id":"pgbouncer-pause-resume-reload","task":"Удерживать подключения во время обслуживания через PgBouncer (PAUSE/RESUME) и перечитать конфигурацию (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 освобождает подключения на стороне PostgreSQL, не разрывая клиентские подключения, и ставит новые запросы в очередь; RESUME снова пропускает очередь. Так кратковременный перезапуск primary или switchover выглядит для приложения как небольшая задержка, а не ошибки.","when":"Во время коротких окон обслуживания (switchover, перезапуск для минорной версии, перезапуск из-за параметров) и при применении изменений pgbouncer.ini/userlist.","caution":"PAUSE возвращает управление только после завершения всех активных транзакций; долгий запрос задерживает PAUSE (при необходимости завершите эту сессию в PostgreSQL). Ожидающие клиенты получают ошибку по истечении query_wait_timeout (по умолчанию 120 s); завершите обслуживание за это время. Забытый RESUME после PAUSE — это полный простой. Некоторые настройки (listen_addr, listen_port и т. д.) не меняются через RELOAD и требуют перезапуска.","untested":true,"tags":["pgbouncer","pause","resume","reload","maintenance"]},{"id":"pgbouncer-pool-mode","task":"Выбор pool_mode в PgBouncer и базовое определение размеров","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 привязывает клиента к одному серверному подключению на всё время его подключения; режим transaction выделяет серверное подключение только на время транзакции и обслуживает тысячи клиентов десятками подключений PostgreSQL; режим statement возвращает подключение после каждого запроса и запрещает транзакции из нескольких операторов. pool_mode можно также переопределить для каждой базы данных.","when":"Режим transaction — для OLTP/микросервисных приложений, открывающих много короткоживущих подключений; режим session — для работы, зависящей от состояния сессии (отчёты, миграции, сервисы с LISTEN).","caution":"В режиме transaction состояние сессии между транзакциями не сохраняется: SET (кроме SET LOCAL), advisory-блокировки уровня сессии, LISTEN, курсоры WITH HOLD, временные таблицы между транзакциями и PREPARE на уровне SQL ненадёжны. Prepared statements на уровне протокола поддерживаются только в PgBouncer 1.21+ при max_prepared_statements > 0 (по умолчанию 200 начиная с 1.24, 0 до этого). default_pool_size, умноженный на число пар (база данных, пользователь), не должен превышать max_connections PostgreSQL (за вычетом запаса superuser_reserved_connections).","untested":true,"tags":["pgbouncer","pool_mode","transaction mode","connection pooling","sizing"]}]},"recipes/postgres-ops.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"recipes":[{"id":"pgss-top-queries","task":"Найти самые дорогие запросы (pg_stat_statements: общее и среднее время)","category":"Производительность","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":"Выводит нормализованные запросы, потребляющие больше всего серверного времени в текущей базе данных, с их долей от общего; второй запрос выводит самые медленные в расчёте на вызов.","when":"Первое, куда смотреть при росте нагрузки CPU/IO или жалобах пользователей 'база тормозит'; чтобы решить, с чего начинать настройку.","caution":"pg_stat_statements ничего не собирает, пока не добавлен в shared_preload_libraries, сервер не перезапущен и в базе не выполнен CREATE EXTENSION. Значения накапливаются с момента последнего сброса. stddev_ms, высокий относительно mean_ms, указывает, что план или распределение данных меняются от вызова к вызову. mean_plan_time заполняется только при pg_stat_statements.track_planning = on (по умолчанию off; включение добавляет накладные расходы). Для просмотра текстов запросов других пользователей нужен суперпользователь или роль pg_read_all_stats.","since":"PostgreSQL 13","note":"В PostgreSQL 13 столбцы total_time/mean_time переименованы в total_exec_time/mean_exec_time, а для времени планирования (total_plan_time) появились отдельные столбцы; в PG12 и ранее используйте старые имена.","tags":["pg_stat_statements","slow query","most expensive query","top sql","performance","query tuning"]},{"id":"pgss-io-temp-heavy","task":"Найти запросы, создающие дисковый I/O, временные файлы и WAL (pg_stat_statements)","category":"Производительность","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":"Показывает запросы, которые читают больше всего блоков вне shared buffers, больше всего пишут во временные файлы (сортировки/хэши, не помещающиеся в work_mem) и генерируют больше всего WAL, вместе с их долей попаданий в кэш.","when":"Чтобы найти ответственный запрос, когда растёт задержка чтения с диска, срабатывают алерты о временных файлах (log_temp_files) или внезапно растёт объём WAL.","caution":"shared_blks_read учитывает и чтения, обслуженные из страничного кэша ОС; реальное время диска требует track_io_timing = on (сначала измерьте его накладные расходы через pg_test_timing). Если temp_written высокий, решение обычно — увеличить work_mem для роли/сессии или исправить запрос/индекс; глобальное увеличение work_mem повышает риск нехватки памяти.","since":"PostgreSQL 13","note":"Столбцы wal_bytes/wal_records появились в PG13. В PG17 blk_read_time/blk_write_time переименованы в shared_blk_read_time/shared_blk_write_time и добавлены local_blk_*_time.","tags":["pg_stat_statements","io","temp files","temp_blks_written","work_mem","wal"]},{"id":"pgss-reset","task":"Сбросить статистику pg_stat_statements (полностью или для одного запроса)","category":"Производительность","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":"Сбрасывает счётчики pg_stat_statements полностью или по пользователю/базе данных/queryid; pg_stat_statements_info показывает время последнего сброса и сколько записей было вытеснено из-за нехватки места.","when":"Чтобы начать чистое измерение перед изменением для настройки (индекс, параметр, деплой) или измерить новый план одного запроса изолированно.","caution":"Сброс нельзя отменить; инструменты мониторинга/APM, опирающиеся на накопительные счётчики, покажут разрыв, поэтому сначала сделайте снимок (см. рецепт про регрессию планов). Если dealloc продолжает расти, pg_stat_statements.max слишком мал (его изменение требует перезапуска). По умолчанию выполнять сброс могут только суперпользователи; выдайте право через GRANT EXECUTE ON FUNCTION.","since":"PostgreSQL 14","note":"Параметризованный сброс (userid, dbid, queryid) появился в PG12, представление pg_stat_statements_info — в PG14. В PG17 добавлен четвёртый параметр minmax_only: pg_stat_statements_reset(0, 0, 0, true) сбрасывает только min/max времени; столбцы stats_since и minmax_stats_since также появились в PG17.","tags":["pg_stat_statements","reset","clear statistics","queryid","statistics","baseline"]},{"id":"auto-explain-setup","task":"Журналировать планы выполнения медленных запросов через auto_explain","category":"Производительность","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":"Записывает в журнал сервера план выполнения запросов, превышающих порог (с фактическим числом строк и данными о буферах при включённом log_analyze); фиксирует план, построенный с реальными параметрами приложения.","when":"Когда запрос медленный только из приложения или с определёнными параметрами и не воспроизводится ручным EXPLAIN; чтобы поймать периодические смены плана.","caution":"log_analyze = on добавляет накладные расходы на инструментирование к каждому запросу в выборке, включая те, что ниже порога; log_timing = off и sample_rate < 1 уменьшают их. log_min_duration = 0 переполняет журнал в production. ALTER SYSTEM SET session_preload_libraries перезаписывает существующее значение; сначала проверьте SHOW session_preload_libraries. session_preload_libraries не влияет на существующие сессии; загрузка в каждом процессе требует shared_preload_libraries и перезапуска.","note":"auto_explain — модуль contrib, CREATE EXTENSION для него не нужен. Также доступны опции log_wal (PG13+) и log_settings (PG12+).","tags":["auto_explain","execution plan","slow query","log","plan capture","explain"]},{"id":"explain-analyze-buffers-wal","task":"Детально разобрать план через EXPLAIN (ANALYZE, BUFFERS, SETTINGS, WAL)","category":"Производительность","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":"Возвращает план с реальной статистикой выполнения: BUFFERS показывает shared hit/read/dirtied и временные блоки по каждому узлу, SETTINGS — параметры со значениями не по умолчанию, влияющие на планирование, WAL — сгенерированные записи/байты WAL.","when":"Чтобы проанализировать разрыв между оценочным и фактическим числом строк, сортировки/хэши, сбрасываемые на диск (Sort Method: external merge, temp read/written), и стоимость DML в WAL.","caution":"ANALYZE действительно выполняет оператор; DML всегда запускайте внутри BEGIN/ROLLBACK (триггеры и приращения последовательностей всё равно срабатывают). Советы по чтению: разрыв в 10 раз и более между оценочным 'rows' и 'actual rows' указывает на проблему статистики; для узлов внутри циклов время и строки указаны на один цикл — для итога умножайте на loops; высокое 'Heap Fetches' в Index Only Scan означает устаревшую visibility map, а 'Workers Planned' > 'Workers Launched' — исчерпанный пул рабочих процессов. Где накладные расходы на замер времени высоки, используйте TIMING OFF; COSTS OFF также скрывает раздел JIT.","since":"PostgreSQL 13","note":"Опция SETTINGS появилась в PG12, WAL — в PG13. В PG16 добавлен GENERIC_PLAN, в PG17 — SERIALIZE и MEMORY; в PG18 BUFFERS включён по умолчанию вместе с ANALYZE.","tags":["explain analyze","buffers","execution plan","reading plans","wal","performance"]},{"id":"plan-regression-queryid","task":"Выявить регрессии планов по queryid (сравнение снимков pg_stat_statements)","category":"Производительность","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":"Сохраняет накопительные счётчики pg_stat_statements в таблицу и сравнивает их с более поздним состоянием по queryid; показывает среднее время и буферы на вызов для вызовов после снимка рядом со старыми значениями.","when":"Чтобы найти запрос, план которого изменился после деплоя, мажорного обновления, обновления статистики или изменения параметров ('вчера было быстро, сегодня медленно').","caution":"Если между снимком и сравнением был выполнен pg_stat_statements_reset() или записи были вытеснены из-за нехватки места (dealloc), дельты бессмысленны; проверьте pg_stat_statements_info.stats_reset. Рост буферов на вызов — более надёжный признак смены плана, чем рост времени, поскольку не зависит от кэширования. Для подтверждения плана выполните EXPLAIN запроса с реальными параметрами или используйте auto_explain; в PostgreSQL нет встроенного закрепления планов (требуется расширение pg_hint_plan). По завершении удалите таблицу снимка.","since":"PostgreSQL 14","note":"pg_stat_activity.query_id и параметр compute_query_id появились в PG14 (по умолчанию auto: вычисляется, когда загружен pg_stat_statements). Строка 'Query Identifier' в выводе EXPLAIN VERBOSE показывает то же значение. Шаги 1 и 2 работают и в PG13.","tags":["plan regression","queryid","pg_stat_statements","snapshot","plan change","performance"]},{"id":"parallel-query-usage","task":"Проверить ёмкость параллельных запросов и текущее использование рабочих процессов","category":"Производительность","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":"Показывает лимиты параллельных запросов и сколько параллельных рабочих процессов использует каждый ведущий процесс прямо сейчас; демонстрирует настройку числа рабочих процессов для таблицы.","when":"Когда аналитические запросы медленнее ожидаемого, в плане 'Workers Planned' > 'Workers Launched' или ядра CPU простаивают.","caution":"max_parallel_workers ограничен max_worker_processes (требуется перезапуск), и этот пул общий с рабочими процессами логической репликации и другими фоновыми процессами. В OLTP высокое max_parallel_workers_per_gather насыщает CPU при конкурентной нагрузке; каждый рабочий процесс также использует собственный work_mem. Запросы, записывающие данные или блокирующие строки (кроме CREATE TABLE AS / SELECT INTO), курсоры и запросы, вызывающие функции PARALLEL UNSAFE, не получают параллельных планов. ALTER TABLE ... SET (parallel_workers) берёт кратковременную ShareUpdateExclusiveLock.","since":"PostgreSQL 13","note":"pg_stat_activity.leader_pid появился в PG13; до этого параллельные рабочие процессы видны только как backend_type = 'parallel worker' и не могут быть связаны со своим ведущим процессом.","tags":["parallel query","parallel worker","leader_pid","max_parallel_workers","gather","cpu"]},{"id":"jit-impact-check","task":"Измерить влияние JIT-компиляции на время запроса","category":"Производительность","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":"Показывает для каждого запроса долю времени JIT-компиляции (generation, inlining, optimization, emission) во времени выполнения; измеряет итоговый эффект, выполняя один и тот же запрос с включённым и выключенным JIT.","when":"Когда короткие OLTP- или отчётные запросы показывают необъяснимые задержки в десятки–сотни ms; особенно на сильно секционированных таблицах, где завышенные оценки стоимости включают JIT.","caution":"JIT включается, только когда стоимость плана превышает jit_above_cost (по умолчанию 100000); при завышенных оценках стоимости компиляция коротких запросов может длиться дольше их выполнения. Вместо глобального отключения сначала рассмотрите ALTER ROLE ... SET jit = off для затронутой роли/базы данных или увеличение jit_above_cost. EXPLAIN (ANALYZE, COSTS OFF) не выводит раздел JIT. Если PostgreSQL собран без поддержки LLVM, JIT никогда не используется; проверьте через SELECT pg_jit_available();","since":"PostgreSQL 15","note":"Столбцы jit_* добавлены в pg_stat_statements в PG15; в PG17 добавлены jit_deform_count/jit_deform_time. JIT появился в PG11 и включён по умолчанию с PG12.","tags":["jit","llvm","jit_above_cost","pg_stat_statements","compilation time","performance"]},{"id":"blocking-tree","task":"Построить дерево блокировок (найти корневой блокирующий процесс)","category":"Сессии","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":"Рекурсивно проходит цепочку ожиданий через pg_blocking_pids(); начиная с корневого блокирующего процесса (который никого не ждёт), показывает каждую ожидающую сессию в виде дерева с отступами, с PID, непосредственно её блокирующими, и возрастом её транзакции.","when":"Когда приложение видит массовые тайм-ауты или растущие очереди; чтобы среди десятков ожидающих найти одну корневую сессию, требующую вмешательства.","caution":"Корнем часто оказывается забытая сессия 'idle in transaction', удерживающая блокировку; столбец query показывает ПОСЛЕДНИЙ оператор этой сессии, а не тот, что взял блокировку. DDL, ожидающий ACCESS EXCLUSIVE, также блокирует простые SELECT, вставшие в очередь за ним, и отображается как промежуточный узел. pg_blocking_pids() кратковременно блокирует менеджер блокировок; не запускайте этот запрос в плотном цикле. Сессия, достижимая по нескольким ветвям, показывается один раз, на самом глубоком уровне.","since":"PostgreSQL 9.6","tags":["blocking","lock tree","lock chain","pg_blocking_pids","blocking session","lock"]},{"id":"lock-wait-detail","task":"Детально изучить ожидания блокировок по объектам и режимам блокировки (pg_locks)","category":"Сессии","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":"Для каждого невыданного запроса блокировки (granted = false) показывает целевой объект (таблица, идентификатор транзакции и т. д.), запрошенный режим, блокирующий PID и его режим, действительно ли блокирующий процесс держит блокировку, и примерное время ожидания.","when":"Чтобы понять, конфликт какой таблицы и какого режима блокировки вызывает блокирование; например, DDL, ожидающий AccessExclusiveLock, или блокировка строки, отображаемая как ожидание transactionid.","caution":"Поскольку сопоставление использует pg_blocking_pids(), выводятся только действительно конфликтующие запросы. blocker_holds = false означает, что блокирующий процесс ещё не держит блокировку, но стоит впереди в очереди с конфликтующим режимом (очередь блокировок). locktype = 'transactionid' — конфликт на уровне строки: ожидающий ждёт завершения транзакции, изменившей строку; сама строка в pg_locks не видна. На больших системах pg_locks может содержать тысячи строк; не запускайте это часто. waiting_for — приближённое значение на основе state_change.","since":"PostgreSQL 9.6","tags":["pg_locks","lock wait","lock waiting","accessexclusivelock","transactionid","lock"]},{"id":"long-idle-transactions","task":"Найти долгие транзакции и транзакции в состоянии 'idle in transaction'","category":"Сессии","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":"Показывает сессии, открытая транзакция которых старше 5 минут или которые простаивают внутри транзакции; с возрастом транзакции, временем в текущем состоянии, назначенным XID и возрастом удерживаемого xmin.","when":"Когда растёт раздувание, VACUUM сообщает 'dead row versions cannot be removed yet', при ожиданиях блокировок или при подозрении на утечку пула подключений.","caution":"Долгие транзакции (особенно REPEATABLE READ/SERIALIZABLE или с открытыми курсорами) через backend_xmin сдерживают горизонт очистки VACUUM (xmin horizon), вызывая раздувание и старение XID во всей базе данных. 'idle in transaction (aborted)' — сессия, получившая ошибку и ожидающая ROLLBACK. Постоянное решение: idle_in_transaction_session_timeout для роли и корректные границы транзакций в приложении.","tags":["long transaction","idle in transaction","xmin","bloat","session","long running"]},{"id":"terminate-idle-in-transaction","task":"Контролируемо массово завершить долгие сессии 'idle in transaction'","category":"Сессии","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":"Сначала выводит сессии, оставившие транзакцию открытой и простаивающей заданное время, затем завершает их; параметр timeout ожидает, пока процесс действительно завершится (или истечёт тайм-аут).","when":"Для экстренного вмешательства, когда забытые сессии, удерживающие блокировки, вызывают блокирование или сдерживают горизонт xmin и останавливают VACUUM.","caution":"pg_cancel_backend ничего не делает для сессии 'idle in transaction' (нет выполняющегося запроса для отмены); нужен pg_terminate_backend, и все незафиксированные изменения откатываются. Сужайте фильтр по usename/application_name; исключите сессии репликации, бэкапа и мониторинга. Пулы подключений заново откроют завершённые подключения. Если тайм-аут истекает, функция выдаёт предупреждение и возвращает false. В качестве постоянного решения задайте idle_in_transaction_session_timeout для роли.","since":"PostgreSQL 14","note":"pg_terminate_backend(pid, timeout) с двумя аргументами появилась в PG14; до этого существует только pg_terminate_backend(pid), которая возвращает управление сразу после отправки сигнала.","tags":["pg_terminate_backend","idle in transaction","terminate session","kill session","bulk","lock"]},{"id":"role-timeouts","task":"Задать statement_timeout / lock_timeout для роли и базы данных","category":"Сессии","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":"Делает параметры тайм-аутов постоянными на уровне роли (и при необходимости базы данных), чтобы каждая нагрузка работала со своими лимитами, не затрагивая глобальную настройку.","when":"Чтобы вышедшие из-под контроля запросы не исчерпывали ресурсы, очереди DDL/блокировок не замораживали приложение, а забытые транзакции не создавали раздувание.","caution":"Настройки применяются только к НОВЫМ сессиям; пересоздайте существующие подключения пула. Пользователи могут переопределить значение через SET в своей сессии; это не граница безопасности. Не задавайте statement_timeout глобально в postgresql.conf: задания обслуживания, такие как pg_dump, VACUUM и CREATE INDEX, тоже будут отменяться. Приоритет: роль+база данных > роль > база данных > postgresql.conf.","note":"idle_in_transaction_session_timeout появился в PG9.6, idle_session_timeout — в PG14, transaction_timeout — в PG17.","tags":["statement_timeout","lock_timeout","idle_in_transaction_session_timeout","alter role","query timeout","timeout"]},{"id":"ddl-lock-timeout-retry","task":"Безопасно применять DDL с lock_timeout и повторными попытками (избегать очередей блокировок)","category":"Сессии","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":"Не даёт DDL, которому нужна ACCESS EXCLUSIVE, бесконечно ждать, если он не может получить блокировку, и блокировать каждый запрос, вставший за ним в очередь (очередь блокировок); повторяет попытки через короткие интервалы.","when":"При применении быстрых, но сильно блокирующих изменений, таких как ALTER TABLE, CREATE/DROP TRIGGER, ATTACH PARTITION или RENAME, на нагруженных таблицах в production.","caution":"Без lock_timeout ALTER TABLE, вставший в очередь за одним долгим SELECT, заставляет ждать и все последующие чтения/записи и может за минуты исчерпать пул подключений. lock_timeout ограничивает только ожидание блокировки; он не ограничивает время выполнения DDL, перезаписывающего таблицу (например, изменение типа столбца). Блок DO — это одна транзакция, удерживающая снимок между попытками; для длительных сценариев повторов предпочтительнее внешний цикл в shell.","tags":["lock_timeout","ddl","alter table","lock queue","retry","lock"]},{"id":"connection-usage","task":"Измерить использование подключений относительно max_connections","category":"Сессии","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":"Показывает использованные слоты подключений и оставшиеся для обычных пользователей относительно max_connections, разбивку по базам данных/пользователям/приложениям/состояниям и настроенные CONNECTION LIMIT.","when":"При ошибках 'too many clients already' / 'remaining connection slots are reserved', при планировании ёмкости или при оценке необходимости пулера.","caution":"max_connections ограничивает только клиентские backend; рабочие процессы autovacuum, фоновые процессы и walsender используют отдельные слоты. Последние superuser_reserved_connections слотов (плюс reserved_connections в PG16+) закрыты для обычных пользователей. Вместо увеличения max_connections до сотен или тысяч используйте пулер, такой как PgBouncer: каждое подключение — отдельный процесс, а затраты памяти и снимков растут с числом подключений. Множество сессий 'idle' указывает на завышенный размер пула приложения. Не давайте вышедшему из-под контроля приложению занять все слоты через ALTER ROLE ... CONNECTION LIMIT; изменение max_connections требует перезапуска.","note":"Параметр reserved_connections и роль pg_use_reserved_connections появились в PG16; запрос работает и в более старых версиях (при отсутствии параметр считается равным 0).","tags":["max_connections","connection count","connection","pooler","superuser_reserved_connections","session"]},{"id":"autovacuum-progress","task":"Мониторинг хода выполняющихся операций autovacuum/VACUUM","category":"Обслуживание","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":"Для каждого выполняющегося VACUUM (рабочий процесс autovacuum или ручной) показывает таблицу, фазу, процент просканированной/очищенной heap, число проходов очистки индексов и wait event; а также число занятых рабочих процессов autovacuum.","when":"Чтобы оценить, когда завершится VACUUM на большой таблице, все ли рабочие процессы autovacuum заняты (таблицы ждут в очереди) и ждёт ли vacuum из-за throttling.","caution":"index_vacuum_count > 1 означает, что список мёртвых кортежей не поместился в память и индексы сканировались несколько раз; увеличьте maintenance_work_mem/autovacuum_work_mem (до PG17 для этого используется максимум 1GB). wait = Timeout:VacuumDelay — это throttling на основе стоимости (autovacuum_vacuum_cost_delay/cost_limit). Не завершайте autovacuum, текст запроса которого содержит 'to prevent wraparound'; он сразу перезапустится. VACUUM FULL и CLUSTER отображаются в pg_stat_progress_cluster, а не в этом представлении.","since":"PostgreSQL 9.6","note":"В PG17 столбцы max_dead_tuples/num_dead_tuples заменены на max_dead_tuple_bytes/num_dead_item_ids и добавлены indexes_total/indexes_processed; поэтому запрос использует столбцы, не зависящие от версии.","tags":["autovacuum","vacuum progress","pg_stat_progress_vacuum","vacuum","maintenance","progress"]},{"id":"xid-wraparound-risk","task":"Измерить риск wraparound идентификаторов транзакций (возраст XID по базам данных и таблицам)","category":"Обслуживание","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":"Показывает для каждой базы данных возраст самого старого незамороженного XID и multixact относительно autovacuum_freeze_max_age и предела wraparound; в текущей базе — самые старые таблицы (включая TOAST) с размером и временем последнего vacuum.","when":"В регулярном мониторинге (типичные пороги: pct_of_wraparound 50% — предупреждение / 75% — критично), когда в журнале появляется 'must be vacuumed within N transactions', и для приоритизации работ по freeze.","caution":"Для таблиц, у которых pct_of_freeze_max_age превышает 100, autovacuum запускает обязательный (anti-wraparound) vacuum; он создаёт высокую нагрузку I/O и перезапускается даже после завершения. Если возраст не снижается, причина почти всегда в чём-то, что сдерживает горизонт xmin (долгая транзакция, prepared transaction, слот репликации, hot_standby_feedback); сначала устраните это, иначе даже VACUUM FREEZE не сможет снизить возраст. Вблизи предела сервер сначала предупреждает, а затем, когда остаётся лишь несколько миллионов, отказывается назначать новые XID (запись). Для точечного freeze: VACUUM (FREEZE, VERBOSE) <table>;","note":"В PG14 добавлены vacuum_failsafe_age / vacuum_multixact_failsafe_age (по умолчанию 1.6 billion): когда таблица достигает этого возраста, VACUUM переходит в аварийный режим, пропуская cost delay и очистку индексов.","tags":["wraparound","xid age","datfrozenxid","relfrozenxid","freeze","multixact"]},{"id":"xmin-horizon-holders","task":"Найти держателей горизонта xmin, мешающих VACUUM удалять мёртвые кортежи","category":"Обслуживание","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":"Выводит, упорядочив по возрасту xmin, четыре источника, которые могут блокировать очистку мёртвых кортежей: сессии, удерживающие снимки, prepared transactions, слоты репликации и standby, отправляющие hot_standby_feedback.","when":"Когда VACUUM VERBOSE сообщает 'dead row versions cannot be removed yet', раздувание продолжает расти, а возраст XID не снижается.","caution":"Самый старый xmin определяет горизонт очистки; слоты репликации и обратная связь от standby влияют на все базы данных. Prepared transactions переживают перезапуски; проверьте владельца и закройте их через COMMIT PREPARED / ROLLBACK PREPARED '<gid>'. Удаление неиспользуемого слота ломает его потребителя (standby, CDC); сначала проверьте владельца. При запуске на standby показывает только сессии этого standby.","tags":["xmin horizon","vacuum blocker","dead tuple","prepared transaction","replication slot","bloat"]},{"id":"pgstattuple-bloat","task":"Измерить раздувание таблиц и индексов через pgstattuple","category":"Обслуживание","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":"Показывает процент живых/мёртвых кортежей и свободного пространства таблицы (быстрая оценка и точное значение) и возвращаемое пространство; для B-tree индексов — плотность листовых страниц и фрагментацию.","when":"Когда оценки n_dead_tup в pg_stat_user_tables недостаточно; чтобы подкрепить данными решение о pg_repack, VACUUM FULL или REINDEX.","caution":"pgstattuple и pgstatindex читают отношение целиком и создают высокую нагрузку I/O на больших таблицах (берут только AccessShareLock). Сначала используйте pgstattuple_approx. По умолчанию требуется суперпользователь или роль pg_stat_scan_tables. Некоторое свободное пространство — норма (fillfactor, место для HOT-обновлений); VACUUM делает его повторно используемым, но не уменьшает файл; для возврата нужен pg_repack или VACUUM FULL (блокировка ACCESS EXCLUSIVE!). Индекс с низким avg_leaf_density перестройте через REINDEX CONCURRENTLY.","since":"PostgreSQL 12","note":"pgstattuple — расширение contrib; pgstattuple_approx появилась в PG9.5. MATERIALIZED в CTE (PG12+) не даёт вызывать pgstatindex (и получать ошибку) для индексов, не являющихся B-tree.","tags":["bloat","pgstattuple","pgstatindex","index bloat","dead tuple","free space"]},{"id":"pg-repack-online","task":"Реорганизовать раздутую таблицу без долгих блокировок (pg_repack)","category":"Обслуживание","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":"В фоне строит уплотнённую копию таблицы, через триггер записывает параллельные изменения в журнальную таблицу и в конце меняет таблицы местами под кратковременной блокировкой ACCESS EXCLUSIVE; в отличие от VACUUM FULL не блокирует чтение/запись на всё время работы.","when":"Когда на работающей системе нужно вернуть сильное раздувание, которое VACUUM вернуть не может (подтверждено через pgstattuple).","caution":"У таблицы должен быть PRIMARY KEY или UNIQUE-индекс по столбцам NOT NULL. Временно требуется дополнительное место на диске, равное таблице вместе с индексами, и создаётся большой объём WAL (отставание реплик, объём архива). Блокировки ACCESS EXCLUSIVE нужны в начале и в конце; по истечении --wait-timeout по умолчанию конфликтующие запросы отменяются, а при необходимости их сессии завершаются, тогда как --no-kill-backend вместо этого пропускает таблицу. Не выполняйте DDL на целевой таблице во время работы. Версии клиента и расширения должны совпадать. При прерывании очистите остатки через DROP EXTENSION pg_repack CASCADE; CREATE EXTENSION pg_repack;","note":"pg_repack не входит в ядро PostgreSQL (отдельный пакет, например postgresql-17-repack).","tags":["pg_repack","bloat","online","vacuum full alternative","table reorganization","repack"]},{"id":"invalid-indexes","task":"Найти и удалить индексы INVALID","category":"Обслуживание","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":"Выводит невалидные индексы, оставшиеся после неудачных запусков CREATE INDEX CONCURRENTLY или REINDEX CONCURRENTLY, с их размером и генерирует команду удаления.","when":"После неудачных или отменённых онлайн-операций с индексами, при регулярных проверках работоспособности и перед мажорным обновлением.","caution":"Выполняющийся CREATE INDEX CONCURRENTLY / REINDEX CONCURRENTLY тоже отображается как INVALID; перед удалением проверьте pg_stat_progress_create_index. Индекс INVALID не используется запросами, но если indisready = true, он по-прежнему обслуживается при каждом INSERT/UPDATE, то есть является чистыми накладными расходами. Его можно перестроить через REINDEX INDEX CONCURRENTLY, но это снова завершится ошибкой, если причина (например, нарушение уникальности) не устранена. Остатки REINDEX CONCURRENTLY имеют суффикс _ccnew/_ccold. DROP INDEX CONCURRENTLY нельзя выполнять внутри блока транзакции.","tags":["invalid index","indisvalid","create index concurrently","broken index","_ccnew","index"]},{"id":"extended-statistics","task":"Создать расширенную статистику для коррелированных столбцов (CREATE STATISTICS)","category":"Обслуживание","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":"Сообщает планировщику о корреляции между столбцами (функциональные зависимости, совместное число различных значений, совместные наиболее частые значения); исправляет оценки строк для многостолбцовых условий WHERE и GROUP BY.","when":"Когда EXPLAIN ANALYZE показывает оценочное число строк намного ниже фактического для многостолбцовых фильтров (например, 12 против 570) и в результате выбирается неверный метод соединения/индекс.","caution":"Действует только после ANALYZE. Когда число комбинаций столбцов намного превышает statistics target (по умолчанию 100), список mcv не может охватить большинство из них, и mcv может не исправить оценку, которую исправили бы dependencies. Проверьте через EXPLAIN; при необходимости уберите mcv из определения или увеличьте target через ALTER STATISTICS customer_city_district_stx SET STATISTICS 1000;. dependencies используется только для условий равенства/IN, ndistinct — для оценок GROUP BY. Каждое определение удлиняет ANALYZE; добавляйте его только для групп столбцов с доказанной ошибкой оценки.","since":"PostgreSQL 12","note":"CREATE STATISTICS появился в PG10 (ndistinct, dependencies), вид mcv — в PG12, ALTER STATISTICS ... SET STATISTICS — в PG13, статистика по выражениям — в PG14.","tags":["extended statistics","create statistics","correlation","row estimate","cardinality","analyze"]},{"id":"analyze-staleness","task":"Найти таблицы с устаревшей статистикой (стратегия ANALYZE)","category":"Обслуживание","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":"Показывает число строк, изменённых с последнего ANALYZE, его отношение к живым строкам и приблизительный порог изменений, при котором autovacuum запустит ANALYZE.","when":"После массовых загрузок/удалений, когда планы показывают неверные оценки строк, или при подозрении, что autoanalyze отстаёт на больших таблицах.","caution":"Вычисленный порог использует глобальные настройки; если существуют reloptions autovacuum_analyze_* для таблицы (pg_class.reloptions), реальный порог отличается. На больших таблицах scale_factor по умолчанию (0.1) срабатывает слишком поздно; снизьте его для таблицы. Autovacuum не выполняет ANALYZE для родительской секционированной таблицы; статистика уровня родителя требует ручного ANALYZE. Временные таблицы autovacuum никогда не видит. ANALYZE читает выборку и берёт ShareUpdateExclusiveLock (не блокирует DML).","tags":["analyze","stale statistics","n_mod_since_analyze","autoanalyze","statistics","planner"]},{"id":"visibility-map-index-only","task":"Проверить покрытие visibility map и эффективность Index Only Scan","category":"Обслуживание","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":"Показывает долю all-visible страниц таблиц, число строк, вставленных после последнего vacuum, и как часто Index Only Scan приходилось обращаться к heap (Heap Fetches).","when":"Когда Index Only Scan работает хуже ожидаемого; на таблицах с преобладанием вставок/append-only и при проверке дизайна покрывающего (INCLUDE) индекса.","caution":"relpages/relallvisible — оценки, записанные последним VACUUM/ANALYZE. Биты visibility map устанавливает только VACUUM; каждый UPDATE/DELETE сбрасывает бит затронутой страницы. При низкой доле на часто обновляемых таблицах снизьте autovacuum_vacuum_scale_factor для таблицы. pg_visibility_map_summary читает небольшой слой VM, а не heap.","since":"PostgreSQL 13","note":"n_ins_since_vacuum и autovacuum, запускаемый вставками (autovacuum_vacuum_insert_threshold/scale_factor), появились в PG13; до этого append-only таблицы могут не получать vacuum вплоть до anti-wraparound vacuum, поэтому visibility map не устанавливается.","tags":["visibility map","index only scan","heap fetches","relallvisible","pg_visibility","vacuum"]},{"id":"range-partition-monthly","task":"Создать таблицу с помесячным range-секционированием (генерация секций в цикле)","category":"Секционирование","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":"Создаёт родительскую таблицу с секционированием RANGE по столбцу даты, индекс, распространяющийся на все секции, и 12 помесячных секций в цикле; затем выводит иерархию с границами и размерами.","when":"Для больших, постоянно растущих таблиц, из которых периодически удаляются старые данные (DETACH/DROP) или запросы к которым в основном фильтруют по диапазону дат (заказы, журналы, временные ряды).","caution":"В секционированной таблице ограничения PRIMARY KEY/UNIQUE должны включать ключ секционирования (глобальных уникальных индексов нет). Нижняя граница включается, верхняя — нет (FROM inclusive, TO exclusive). Без задания, заранее создающего будущие секции (pg_cron, pg_partman), INSERT вне диапазона завершаются ошибкой. Тысячи секций увеличивают время планирования и накладные расходы на блокировки. CREATE INDEX CONCURRENTLY на родительской таблице не поддерживается: используйте CREATE INDEX ... ON ONLY на родителе, CREATE INDEX CONCURRENTLY на каждой секции, затем ALTER INDEX ... ATTACH PARTITION.","since":"PostgreSQL 12","note":"Декларативное секционирование появилось в PG10, индексы/PRIMARY KEY на родителе и секции по умолчанию — в PG11, pg_partition_tree() — в PG12.","tags":["partition","range partition","declarative partitioning","monthly partition","pg_partition_tree","partitioning"]},{"id":"attach-partition-safely","task":"Подключить подготовленную таблицу как секцию с короткой блокировкой (ATTACH PARTITION)","category":"Секционирование","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":"Превращает данные, загруженные в отдельную таблицу, в секцию без полного сканирования и долгих блокировок, используя заранее проверенное ограничение CHECK, соответствующее границам, и заранее построенные индексы.","when":"Для массовых загрузок (ETL) или восстановления из архива: подготовьте данные, не трогая рабочую таблицу, затем введите их в работу одним шагом.","caution":"ATTACH берёт SHARE UPDATE EXCLUSIVE на родителе и ACCESS EXCLUSIVE на подключаемой таблице. Без подходящего валидного CHECK таблица полностью сканируется под этой блокировкой для проверки границ; если индексы, соответствующие индексам родителя, отсутствуют, ATTACH строит их под блокировкой, поэтому подготовьте и то, и другое заранее. Если существует секция по умолчанию, она тоже сканируется, чтобы доказать, что в ней нет строк нового диапазона (см. рецепт про секцию по умолчанию). Имена и типы столбцов должны точно совпадать с родителем; создание таблицы через LIKE ... INCLUDING — самый надёжный способ.","since":"PostgreSQL 12","note":"ATTACH PARTITION, берущий на родителе только SHARE UPDATE EXCLUSIVE, появился в PG12; в PG11 и ранее родитель блокируется ACCESS EXCLUSIVE.","tags":["attach partition","add partition","check constraint","not valid","etl","partitioning"]},{"id":"detach-partition-concurrently","task":"Отключить старую секцию, не блокируя рабочий трафик (DETACH CONCURRENTLY)","category":"Секционирование","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":"Отключает секцию в двух транзакциях; берёт на родителе только SHARE UPDATE EXCLUSIVE, поэтому выполняющиеся запросы не блокируются, и добавляет к отключённой таблице ограничение CHECK, эквивалентное границе секции.","when":"При отключении старых периодов на работающей системе для хранения/архивирования (с последующим DROP, переносом в другое табличное пространство или архивом pg_dump).","caution":"Нельзя выполнять внутри блока BEGIN ... COMMIT и нельзя использовать, если у родителя есть секция по умолчанию. Ожидает все транзакции, использующие родителя и начавшиеся до неё; долгие транзакции её затягивают. При прерывании секция остаётся скрытой от запросов, но не отключённой ('detach pending'), и её нужно завершить через FINALIZE. Обычный DETACH без CONCURRENTLY быстрее, но требует ACCESS EXCLUSIVE на родителе; пробуйте его с lock_timeout.","since":"PostgreSQL 14","tags":["detach partition","concurrently","finalize","retention","archiving","partitioning"]},{"id":"default-partition-pitfall","task":"Ловушка секции по умолчанию: создание новой секции завершается ошибкой 'would be violated by some row'","category":"Секционирование","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":"Подсчитывает строки, попавшие в секцию по умолчанию, по периодам; переносит строки из диапазона новой секции в новую таблицу и подключает её — всё в одной транзакции.","when":"Когда CREATE TABLE ... PARTITION OF или ATTACH завершается ошибкой 'updated partition constraint for default partition would be violated by some row' или обнаруживается, что секция по умолчанию растёт.","caution":"При наличии секции по умолчанию каждая новая секция полностью сканирует секцию по умолчанию, удерживая на ней ACCESS EXCLUSIVE; большая секция по умолчанию означает простой при каждом обслуживании. Пока строки переносятся, секция по умолчанию закрыта для записи; делайте это при низком трафике. Постоянное решение: заранее создавайте секции и держите секцию по умолчанию пустой с мониторингом (алерт при числе строк > 0) или вовсе её не используйте, чтобы INSERT вне диапазона завершались ошибкой и проблема проявлялась рано. Секция по умолчанию также препятствует DETACH ... CONCURRENTLY.","since":"PostgreSQL 11","tags":["default partition","partition error","violated by some row","partition constraint","pitfall","partitioning"]},{"id":"partition-pruning-check","task":"Проверить, что работает отсечение секций (partition pruning) на этапе планирования и выполнения","category":"Секционирование","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":"Сравнивает отсечение на этапе планирования, отсечение на этапе выполнения с generic plan ('Subplans Removed: N') и то, как отсечение теряется при применении функции к ключу секционирования.","when":"Когда запросы к секционированной таблице медленнее ожидаемого, план сканирует все секции или приложение использует ORM/prepared statements.","caution":"Применение функции или приведения типа к ключу секционирования (date_trunc(created_at), created_at::text) отключает отсечение; записывайте условие как диапазон по ключу. С generic plan отсечение происходит при запуске исполнителя и видно только как 'Subplans Removed'; для STABLE-выражений, таких как now(), оно видно и в EXPLAIN без ANALYZE. При enable_partition_pruning = off отсечение не выполняется вовсе. При большом числе секций измеряйте также Planning Time.","since":"PostgreSQL 12","note":"Отсечение на этапе выполнения появилось в PG11; plan_cache_mode — в PG12, где также значительно улучшилась производительность планирования при большом числе секций.","tags":["partition pruning","subplans removed","generic plan","plan_cache_mode","explain","partitioning"]},{"id":"pg-partman-setup","task":"Автоматизировать создание секций и их хранение через pg_partman","category":"Секционирование","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":"Регистрирует родительскую таблицу в pg_partman; заранее создаёт будущие секции, удаляет вышедшие за срок хранения и сообщает о строках, попавших в секцию по умолчанию.","when":"Когда нужна стандартная, наблюдаемая автоматизация вместо ручного управления ежедневными/ежемесячными секциями или собственных скриптов (таблицы журналов, метрик, событий).","caution":"API изменился между pg_partman 4.x и 5.x: в 5.x p_type принимает только 'range'/'list' (по умолчанию 'range'), а p_type := 'native' из 4.x недопустим; проверьте установленную версию через SELECT extversion FROM pg_extension WHERE extname = 'pg_partman';. create_parent по умолчанию также создаёт секцию по умолчанию. retention_keep_table = false безвозвратно удаляет секции через DROP; если нужен архив, оставьте true и используйте retention_schema. Если run_maintenance_proc() не запланирован, новые секции не создаются и данные попадают в секцию по умолчанию. pg_partman_bgw требует shared_preload_libraries и перезапуска.","note":"pg_partman не входит в ядро PostgreSQL (отдельный пакет, например postgresql-17-partman). Команды используют синтаксис pg_partman 5.x.","tags":["pg_partman","partition maintenance","retention","create_parent","automatic partitioning","partitioning"]},{"id":"least-privilege-roles","task":"Построить иерархию ролей с минимальными привилегиями (GRANT + ALTER DEFAULT PRIVILEGES)","category":"Безопасность","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":"Строит иерархию групповых ролей: владельца, только чтение и чтение-запись; выдаёт нужные привилегии на существующие и будущие таблицы/последовательности и авторизует пользователей приложения только через членство в группах.","when":"При настройке новой схемы приложения, исправлении приложений, подключающихся как суперпользователь или владелец таблиц, и упрощении привилегий перед аудитом.","caution":"ALTER DEFAULT PRIVILEGES применяется только к объектам, создаваемым ПОЗЖЕ ролью, указанной в FOR ROLE; если инструмент миграций создаёт таблицы от другого пользователя, привилегии не применяются, поэтому выполняйте DDL с SET ROLE app_owner. GRANT ... ON ALL TABLES охватывает только объекты, существующие на этот момент. Базы данных, созданные в PG15+, уже не выдают PUBLIC право CREATE на схему public; базам, обновлённым со старых версий, нужен REVOKE. REVOKE ALL ON DATABASE ... FROM PUBLIC также снимает CONNECT и TEMP; выдайте их отдельно другим ролям, которым они нужны. Используйте psql \\password вместо указания паролей в командной строке (они не попадут в журналы и историю).","note":"Роли pg_read_all_data / pg_write_all_data появились в PG14, опции INHERIT/SET для членства в ролях (GRANT ... WITH INHERIT FALSE) — в PG16.","tags":["least privilege","role hierarchy","alter default privileges","grant","privilege","security"]},{"id":"row-level-security","task":"Настроить изоляцию арендаторов (tenant) через row-level security","category":"Безопасность","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":"Включает RLS на таблице и определяет политику, которая фильтрует строки по идентификатору арендатора из переменной сессии (USING) и блокирует запись для другого арендатора (WITH CHECK).","when":"Чтобы обеспечить изоляцию данных в multi-tenant-приложениях на уровне базы данных, а не полагаться на код приложения.","caution":"Суперпользователи и роли с атрибутом BYPASSRLS всегда обходят политики; на владельца таблицы они распространяются только с FORCE ROW LEVEL SECURITY. При включённом RLS и отсутствии подходящей политики результат пуст (без ошибки). current_setting(..., true) возвращает NULL, если переменная не определена; NULLIF не даёт пустой строке, оставшейся после RESET, вызвать ошибку как ''::int. Пользователи могут изменить app.tenant_id через SET; задавать его должен только доверенный уровень приложения, и конечные пользователи не должны получать доступ к SQL. С пулером в режиме transaction используйте SET LOCAL вместо SET. Проиндексируйте столбец tenant_id, используемый в политике. pg_dump завершается ошибкой при запуске от пользователя, на которого распространяется RLS; снимайте бэкапы ролью с BYPASSRLS.","since":"PostgreSQL 9.5","tags":["row level security","rls","create policy","multi-tenant","tenant isolation","security"]},{"id":"pg-hba-validate-reload","task":"Проверить порядок правил pg_hba.conf и безопасно перечитать конфигурацию","category":"Безопасность","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":"Показывает, где находится pg_hba.conf, как PostgreSQL его интерпретирует (строка, тип, база данных, пользователь, адрес, метод) и синтаксические ошибки до reload; затем перечитывает конфигурацию.","when":"При добавлении новой клиентской сети/приложения, переходе с md5 на scram-sha-256 или расследовании ошибок 'no pg_hba.conf entry for host'.","caution":"pg_hba_file_rules показывает файл в текущем виде, а не действующие правила. Если в какой-либо строке error не пуст, reload вообще НЕ загружает pg_hba.conf, и продолжают действовать старые правила (в журнале: 'pg_hba.conf was not reloaded'). Изменения влияют только на новые подключения. Широкое правило, размещённое в начале (например, host all all 0.0.0.0/0 trust), нейтрализует все ограничения после него. При выключенном SSL строки hostssl не могут совпасть и показывают сообщение в столбце error. По умолчанию читать представление могут только суперпользователи. Убедитесь, что инструменты, редактирующие файл от root, не меняют его владельца; если пользователь postgres не может его прочитать, reload завершается ошибкой.","since":"PostgreSQL 10","note":"pg_hba_file_rules появилось в PG10; в новых версиях есть также столбцы rule_number и file_name. PG16 поддерживает директивы include в pg_hba.conf и сопоставление по регулярным выражениям в полях user/database.","tags":["pg_hba.conf","authentication","rule order","reload","pg_hba_file_rules","security"]},{"id":"ssl-enable-verify","task":"Включить SSL/TLS и проверить шифрование подключений (pg_stat_ssl)","category":"Безопасность","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":"Включает TLS с сертификатом сервера без перезапуска, задаёт минимальную версию протокола и показывает, зашифрованы ли текущие подключения, с версией TLS и шифром.","when":"Для требований аудита безопасности/соответствия (PCI, GDPR), для защиты паролей и данных при передаче по сети.","caution":"Одного ssl = on недостаточно для принудительного шифрования; клиенты по-прежнему могут подключаться без шифрования с sslmode=prefer/disable. Для принудительного шифрования используйте в pg_hba.conf hostssl вместо host (и добавьте hostnossl ... reject). sslmode=require не проверяет подлинность сервера (уязвимо для MITM); для проверки сертификата используйте verify-full. Файл ключа должен принадлежать пользователю postgres с правами 0600 (или root с 0640); иначе SSL не загружается, поэтому после reload проверьте журнал и SHOW ssl. Обновление сертификатов также подхватывается через reload.","since":"PostgreSQL 10","note":"Изменение настроек SSL через reload вместо перезапуска появилось в PG10, ssl_min_protocol_version — в PG12; начиная с PG13 его значение по умолчанию — TLSv1.2.","tags":["ssl","tls","pg_stat_ssl","hostssl","certificate","encryption"]},{"id":"scram-migration","task":"Перейти с паролей md5 на scram-sha-256","category":"Безопасность","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":"Обеспечивает хранение новых паролей как SCRAM-SHA-256, находит login-роли, у которых ещё хранятся хэши md5, и описывает шаги перевода pg_hba.conf на scram-sha-256 после обновления паролей.","when":"Когда аудит безопасности отмечает md5, после обновления до PG14+ или при подготовке к версиям, где md5 объявлен устаревшим.","caution":"SCRAM нельзя получить из существующих хэшей md5; каждый пароль нужно задать заново в открытом виде (требует координации с командами приложений). Метод 'md5' в pg_hba.conf автоматически выполняет аутентификацию SCRAM для ролей с паролем SCRAM, поэтому сохранение md5 на время миграции — безопасный промежуточный шаг, но как только метод становится 'scram-sha-256', роли с паролями md5 больше не смогут подключиться. Старые клиентские драйверы (libpq < 10, старые драйверы JDBC/.NET) могут не поддерживать SCRAM; сначала обновите драйверы. ALTER ROLE ... PASSWORD может записать пароль в журнал через log_statement; psql \\password хэширует его на клиенте перед отправкой.","since":"PostgreSQL 10","note":"SCRAM-SHA-256 появился в PG10, а значением password_encryption по умолчанию scram-sha-256 стал в PG14. В PG18 пароли md5 объявлены устаревшими и выдаётся предупреждение.","tags":["scram-sha-256","md5","password_encryption","password","authentication","security"]},{"id":"pgaudit-object-audit","task":"Вести журналы аудита на уровне ролей и объектов через pgaudit","category":"Безопасность","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":"Записывает DDL и изменения привилегий по всему серверу, все чтения/записи определённой роли и доступ к чувствительным таблицам/столбцам, выданным роли аудитора, в виде структурированных строк журнала с префиксом 'AUDIT:'.","when":"Чтобы доказать, кто обращался к чувствительным данным, при аудитах соответствия, таких как GDPR, PCI-DSS или SOX.","caution":"Включение pgaudit.log = 'all' или 'read' на весь сервер резко увеличивает объём журналов и I/O; сужайте область через аудит ролей или объектов. Суперпользователь может отключить аудит (ALTER SYSTEM/ALTER ROLE); отправляйте записи в центральную систему журналов (syslog/SIEM) и храните их независимо от сервера базы данных. log_parameter = on также журналирует bind-параметры (персональные данные). Настройки ролей вступают в силу в новых сессиях. Мажорная версия pgaudit должна совпадать с мажорной версией PostgreSQL (pgaudit 17.x = PG17).","note":"pgaudit не входит в ядро PostgreSQL (отдельный пакет, например postgresql-17-pgaudit).","tags":["pgaudit","audit","auditing","object audit","log","security"]},{"id":"memory-settings-check","task":"Проверить настройки памяти (shared_buffers, work_mem, maintenance_work_mem)","category":"Параметры","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":"Показывает параметры, связанные с памятью, с их значением, источником (по умолчанию/файл конфигурации) и контекстом изменения (postmaster = перезапуск); приблизительные потолки памяти для work_mem и autovacuum; объём временных файлов по базам данных.","when":"При вводе нового сервера, после добавления RAM, после срабатывания OOM killer или при интенсивном использовании временных файлов.","caution":"work_mem выделяется не на подключение, а на УЗЕЛ сортировки/хэширования (для хэшей — с множителем hash_mem_multiplier) и отдельно для каждого параллельного рабочего процесса; сложный запрос может использовать несколько work_mem, поэтому вычисленное произведение — грубое предупреждение, а не верхняя граница. Типичные начальные значения: shared_buffers ~25% RAM (требуется перезапуск), effective_cache_size ~50-75% (лишь подсказка планировщику, ничего не выделяет), maintenance_work_mem 512MB-2GB. При autovacuum_work_mem = -1 каждый рабочий процесс autovacuum может использовать maintenance_work_mem. Если temp_bytes высокий, увеличивайте work_mem для роли или запроса, а не глобально. При больших shared_buffers в Linux рассмотрите huge_pages.","note":"hash_mem_multiplier появился в PG13 (по умолчанию 2.0 начиная с PG15).","tags":["shared_buffers","work_mem","maintenance_work_mem","memory","memory tuning","temp_bytes"]},{"id":"pending-restart-config-errors","task":"Показать параметры, ожидающие перезапуска, и ошибки файлов конфигурации","category":"Параметры","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":"Показывает параметры, изменённые в файлах, но ожидающие перезапуска, строки файлов конфигурации, которые не применились или содержат ошибки, и из какого файла/строки берётся действующее значение параметра.","when":"Чтобы после ALTER SYSTEM или изменения postgresql.conf убедиться, что reload дал ожидаемый эффект, и вывести ожидающие изменения перед плановым перезапуском.","caution":"pg_file_settings читает файлы в текущем виде; если параметр определён в нескольких местах, applied = true только у последнего (postgresql.auto.conf читается последним, поэтому ALTER SYSTEM всегда побеждает). Строка с applied = false и пустым error перекрыта другим определением; это не ошибка. Параметр, требующий перезапуска, показывает error 'setting could not be applied'. Ошибочный файл при reload может примениться частично; после изменения выполните pg_reload_conf(), затем проверьте эти запросы и журнал сервера. Не редактируйте postgresql.auto.conf вручную; используйте ALTER SYSTEM RESET.","since":"PostgreSQL 9.5","tags":["pending_restart","pg_file_settings","restart","configuration error","alter system","config"]},{"id":"checkpoint-stats","task":"Проверить поведение checkpoint (PG17+ pg_stat_checkpointer, PG16 и ранее pg_stat_bgwriter)","category":"Параметры","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":"Показывает соотношение плановых checkpoint и checkpoint, вынужденных объёмом WAL, среднее время записи/синхронизации на checkpoint и объём записанных буферов (в PG16 и ранее — вместе с записью bgwriter и backend).","when":"Когда в периоды интенсивной записи наблюдаются всплески I/O, генерация WAL высока или журнал предупреждает 'checkpoints are occurring too frequently'.","caution":"Высокий req_pct (обычно выше 10-20%) означает, что checkpoint вынуждены, потому что заполняется max_wal_size; увеличьте max_wal_size и следите за строками 'checkpoint starting: wal' при log_checkpoints = on. Более длинный checkpoint_timeout распределяет I/O, но удлиняет crash recovery. В PG16 и ранее высокий written_by_backends означает, что backend сами вынуждены записывать грязные страницы (недостаточно shared_buffers/bgwriter); в PG17 это видно в pg_stat_io. Средние значения учитывают и плановые checkpoint, пропущенные из-за отсутствия работы, поэтому воспринимайте их как приблизительные. Счётчики накапливаются с момента stats_reset.","since":"PostgreSQL 17","note":"В PG17 статистика checkpointer перенесена из pg_stat_bgwriter в новое представление pg_stat_checkpointer (checkpoints_timed → num_timed, checkpoints_req → num_requested, buffers_checkpoint → buffers_written); buffers_backend и buffers_backend_fsync удалены. В PG18 добавлены столбцы num_done и slru_written.","tags":["checkpoint","pg_stat_checkpointer","pg_stat_bgwriter","max_wal_size","checkpoint_timeout","wal"]},{"id":"relation-size-breakdown","task":"Вывести размер таблиц с разбивкой на heap / индексы / TOAST и самые большие индексы","category":"Хранение","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":"Показывает самые большие таблицы с разбивкой на общий объём, heap, индексы и TOAST (+FSM/VM) и их долю в базе данных; а также самые большие индексы с числом сканирований и признаками unique/primary.","when":"Чтобы при росте использования диска найти, откуда берётся рост (данные, индексы или TOAST), и выявить большие неиспользуемые индексы.","caution":"Функции размера обращаются к файловой системе для каждого отношения и могут работать медленно в базах с десятками тысяч таблиц. Секционированный родитель (relkind 'p') сам занимает 0 байт и исключён; для итога по иерархии: SELECT pg_size_pretty(sum(pg_total_relation_size(relid))) FROM pg_partition_tree('sales'); Большие индексы с idx_scan = 0 — кандидаты на удаление, но счётчики охватывают только время с последнего сброса статистики и только этот сервер (использование на репликах не видно); индексы unique/primary обеспечивают ограничения и не удаляются.","tags":["table size","index size","toast","disk usage","pg_total_relation_size","storage"]},{"id":"extension-update-check","task":"Найти расширения с ожидающими обновлениями и обновить их","category":"Расширения","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":"Сравнивает установленную в базе версию каждого расширения с версией по умолчанию пакета на сервере и генерирует команду ALTER EXTENSION для расширений с ожидающими обновлениями.","when":"После минорных/мажорных обновлений PostgreSQL и обновлений пакетов расширений, а также при периодическом обслуживании.","caution":"Обновление пакета ОС не обновляет SQL-часть автоматически; ALTER EXTENSION ... UPDATE нужно выполнить в каждой базе данных отдельно (особенно важно после pg_upgrade). Скрипт обновления может брать блокировки и изменять объекты (например, PostGIS); сначала протестируйте его и прочитайте release notes. Для перехода на конкретную версию: ALTER EXTENSION pgstattuple UPDATE TO '1.5'; Некоторые расширения (например, pg_stat_statements) также требуют перезапуска для перезагрузки разделяемой библиотеки.","tags":["alter extension update","extension version","extension","pg_available_extensions","upgrade","update"]},{"id":"pg-stat-io-analysis","task":"Проанализировать источники I/O через pg_stat_io (тип backend, объект, контекст)","category":"Расширения","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":"Показывает чтения, записи, расширения, вытеснения и fsync по типу backend (client backend, autovacuum, checkpointer...), объекту (relation/temp relation) и контексту (normal, vacuum, bulkread, bulkwrite); и от каких типов процессов идёт запись.","when":"При узком месте I/O — чтобы понять, откуда нагрузка: от запросов, autovacuum или checkpoint, и для проверки настроек shared_buffers/bgwriter.","caution":"pg_stat_io действует на весь кластер и накапливается с последнего сброса; чтобы увидеть изменение, берите разницу двух выборок (для сброса: SELECT pg_stat_reset_shared('io');). read_time/write_time заполняются только при track_io_timing = on. reads включает и чтения, обслуженные из страничного кэша ОС. Высокие writes или evictions у client backend означают, что backend сами записывают буферы, чтобы освободить место; проверьте shared_buffers, bgwriter_lru_maxpages и настройки checkpoint. Строки с context = 'vacuum'/'bulkread' отражают использование кольцевого буфера; reuses там — норма.","since":"PostgreSQL 16","note":"pg_stat_io появилось в PG16. В PG18 удалён столбец op_bytes, добавлены read_bytes/write_bytes/extend_bytes, и в этом представлении также отображается I/O WAL; в PG18 используйте read_bytes вместо 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":"Переключить (ротировать) бинарный журнал","category":"Binlog и журналы","command":"FLUSH BINARY LOGS;","what":"Закрывает текущий файл бинарного журнала и переключается на новый.","when":"Чтобы создать чистую начальную точку binlog перед бэкапом или ограничить размер файла.","caution":"Частая ротация создаёт много мелких файлов binlog. Та же команда работает в MariaDB; FLUSH LOGS ротирует все журналы.","tags":["binlog","rotate","flush","log"]},{"id":"purge-binlog","task":"Удалить старые бинарные журналы (PURGE)","category":"Binlog и журналы","command":"PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;","what":"Удаляет с диска файлы бинарного журнала старше указанной даты.","when":"Когда binlog заполняют диск, для очистки старых, более не нужных журналов.","caution":"Если удаляемый binlog ещё нужен реплике, репликация сломается; сначала проверьте через SHOW REPLICA STATUS, что все реплики прошли его. Для удаления до определённого файла: PURGE BINARY LOGS TO 'mysql-bin.000123'; Для автоматической очистки используйте настройку binlog_expire_logs_seconds.","tags":["binlog","purge","cleanup","log"]},{"id":"show-binlogs","task":"Просмотреть состояние бинарного журнала","category":"Binlog и журналы","command":"SHOW BINARY LOGS;","what":"Выводит все файлы бинарного журнала на сервере и их размеры.","when":"Чтобы увидеть использование диска binlog и какие файлы существуют.","caution":"Для текущего записываемого файла binlog и его позиции: SHOW BINARY LOG STATUS; (MySQL 8.2+). В более старых версиях используется SHOW MASTER STATUS (объявлен устаревшим в 8.2–8.3, удалён в 8.4).","tags":["binlog","status","show","log"]},{"id":"enable-general-slow-log","task":"Включить общий журнал и журнал медленных запросов","category":"Binlog и журналы","command":"SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL general_log = 'ON';","what":"Включает во время работы журнал медленных запросов (запросы длительностью более 1 секунды) и общий журнал, записывающий каждый запрос.","when":"Чтобы временно проанализировать медленные запросы или весь трафик запросов.","caution":"general_log добавляет значительные накладные расходы на производительность и диск; всегда отключайте его после диагностики (SET GLOBAL general_log='OFF'). Эти настройки сбрасываются при перезапуске; чтобы сделать их постоянными, используйте SET PERSIST.","tags":["slow-log","general-log","log","diagnostics"]},{"id":"innodb-redo-capacity","task":"Изменить размер redo log InnoDB","category":"Хранение","command":"SET GLOBAL innodb_redo_log_capacity = 2147483648;","what":"Динамически задаёт во время работы общую ёмкость redo log InnoDB (в байтах, здесь 2 GB).","when":"Чтобы снизить частоту checkpoint и повысить производительность при нагрузке с интенсивной записью.","caution":"innodb_redo_log_capacity появился в MySQL 8.0.30 и заменяет старые настройки innodb_log_file_size + innodb_log_files_in_group. В более старых версиях/MariaDB innodb_log_file_size меняется в my.cnf и требует перезапуска. Чтобы сделать постоянным: SET PERSIST innodb_redo_log_capacity = 2147483648;","since":"MySQL 8.0.30","note":"innodb_redo_log_capacity появился в MySQL 8.0.30 и позволил динамически (без перезапуска) менять размер redo log; ранее требовалось задавать innodb_log_file_size + innodb_log_files_in_group и перезапускать сервер.","tags":["innodb","redo-log","capacity","storage"]},{"id":"innodb-undo-tablespace","task":"Добавить / управлять undo tablespace InnoDB","category":"Хранение","command":"CREATE UNDO TABLESPACE undo_003 ADD DATAFILE 'undo_003.ibu';","what":"Создаёт новое undo tablespace InnoDB; undo log управляются отдельно от файлов данных и становятся усекаемыми.","when":"Чтобы управлять пространством, когда долгие транзакции раздувают undo log, или вынести undo на отдельный диск.","caution":"Имя файла должно оканчиваться на .ibu. Перед удалением undo tablespace его нужно сделать неактивным: ALTER UNDO TABLESPACE undo_003 SET INACTIVE; затем DROP UNDO TABLESPACE undo_003; Это возможность MySQL 8.0+.","since":"MySQL 8.0","note":"Создание/удаление undo tablespace через SQL (CREATE/DROP UNDO TABLESPACE) появилось в MySQL 8.0; ранее число undo tablespace было фиксировано параметром innodb_undo_tablespaces и не могло управляться во время работы.","tags":["innodb","undo-log","tablespace","storage"]},{"id":"create-tablespace","task":"Добавить general tablespace","category":"Хранение","command":"CREATE TABLESPACE ssd_ts ADD DATAFILE 'ssd_ts.ibd' ENGINE=InnoDB;","what":"Создаёт общее табличное пространство (general tablespace) InnoDB, которое могут совместно использовать несколько таблиц.","when":"Чтобы сгруппировать таблицы в определённом файле/на определённом диске или сократить число файлов.","caution":"Если путь DATAFILE относительный, файл создаётся в datadir; для размещения на другом диске нужен абсолютный путь и соответствующие права. Чтобы перенести сюда таблицу: ALTER TABLE customer TABLESPACE ssd_ts; Табличное пространство можно удалить, только когда оно пустое.","tags":["tablespace","innodb","storage","create"]},{"id":"optimize-table","task":"Выполнить OPTIMIZE для таблицы","category":"Хранение","command":"OPTIMIZE TABLE customer;","what":"Перестраивает таблицу, чтобы устранить фрагментацию, вернуть неиспользуемое пространство и обновить статистику индексов.","when":"Чтобы вернуть пространство в таблице InnoDB, раздутой множеством DELETE/UPDATE.","caution":"Для InnoDB OPTIMIZE фактически выполняется как онлайн-ALTER, перестраивающий таблицу, но ему всё равно нужны I/O и временное место на диске; для больших таблиц требуется свободное место, равное размеру таблицы. Запускайте при низком трафике, чтобы уменьшить блокировки.","tags":["optimize","table","defragment","storage"]},{"id":"table-schema-size","task":"Запросить размеры таблиц и схем","category":"Хранение","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":"Выводит 20 самых больших таблиц по суммарному размеру данных и индексов (MB).","when":"Для анализа использования диска и поиска таблиц, занимающих место.","caution":"Размеры в information_schema для InnoDB приблизительны. Для итогов по схемам группируйте только по table_schema (GROUP BY), без table_name.","tags":["size","table","schema","information-schema"]},{"id":"analyze-table","task":"Обновить статистику таблицы (ANALYZE TABLE)","category":"Обслуживание","command":"ANALYZE TABLE customer;","what":"Пересчитывает статистику распределения индексов/ключей таблицы, чтобы оптимизатор мог выбирать лучшие планы.","when":"Когда после массовых изменений данных появляются плохие планы запросов.","caution":"Во время выполнения на таблицу берётся кратковременная блокировка метаданных. В InnoDB статистика основана на выборке; для большей точности увеличьте innodb_stats_persistent_sample_pages.","tags":["analyze","statistics","optimizer","maintenance"]},{"id":"rebuild-table","task":"Перестроить таблицу/индексы","category":"Обслуживание","command":"ALTER TABLE customer ENGINE=InnoDB;","what":"Перестраивает таблицу с тем же движком; пересоздаёт индексы и страницы данных, устраняя фрагментацию и раздувание индексов.","when":"Чтобы обновить таблицу пустым ALTER или исправить повреждённую структуру страниц.","caution":"Для больших таблиц требуется временное место на диске, равное размеру таблицы. Чтобы уменьшить блокировки: ALTER TABLE customer ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;","tags":["rebuild","alter","innodb","maintenance"]},{"id":"check-table","task":"Проверить целостность таблицы (CHECK TABLE)","category":"Обслуживание","command":"CHECK TABLE customer;","what":"Проверяет таблицу и её индексы на повреждения и возвращает статус.","when":"При неожиданных ошибках или подозрении на повреждение.","caution":"Во время проверки на таблицу берётся блокировка чтения. При обнаружении повреждения InnoDB обычно восстанавливают через dump/restore; REPAIR TABLE работает только для таких движков, как MyISAM/Archive.","tags":["check","integrity","corruption","maintenance"]},{"id":"set-global-persist","task":"Изменить глобальную переменную и сохранить её постоянно","category":"Параметры","command":"SET PERSIST innodb_buffer_pool_size = 4294967296;","what":"Устанавливает переменную и на работающем сервере, и постоянно — с сохранением при перезапусках (mysqld-auto.cnf).","when":"Чтобы постоянно изменить серверную переменную без перезапуска.","caution":"SET PERSIST — возможность MySQL 8.0+; MariaDB её не поддерживает (там отредактируйте my.cnf и перезапустите). Для изменения только во время работы используйте SET GLOBAL. Переменные, применяемые только при перезапуске, требуют SET PERSIST_ONLY.","since":"MySQL 8.0","note":"SET PERSIST появился в MySQL 8.0 и делает переменную постоянной, записывая её в mysqld-auto.cnf; ранее (и в MariaDB) для постоянства требовалось вручную редактировать my.cnf и перезапускать сервер.","tags":["set-persist","global","variable","parameter"]},{"id":"show-variables","task":"Просмотреть текущее значение переменной","category":"Параметры","command":"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';","what":"Показывает действующее значение указанной серверной переменной (или переменных, соответствующих шаблону).","when":"Чтобы проверить текущее значение параметра.","caution":"Для глобального значения, а не значения сессии, используйте SHOW GLOBAL VARIABLES. Чтобы увидеть, откуда взято значение (файл, командная строка, 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":"Перечитать таблицы привилегий (FLUSH PRIVILEGES)","category":"Параметры","command":"FLUSH PRIVILEGES;","what":"Перезагружает в память таблицы привилегий, такие как mysql.user, после их прямого изменения.","when":"Чтобы активировать изменения, сделанные ручными INSERT/UPDATE в таблицах привилегий.","caution":"GRANT/REVOKE/CREATE USER и так применяют привилегии немедленно; FLUSH PRIVILEGES после них не нужен. Он требуется только при прямом изменении таблиц привилегий.","tags":["flush-privileges","grants","privileges","reload"]},{"id":"setup-replica","task":"Настроить реплику (CHANGE REPLICATION SOURCE)","category":"Репликация","command":"CHANGE REPLICATION SOURCE TO SOURCE_HOST='source_host', SOURCE_PORT=3306, SOURCE_USER='replicator', SOURCE_PASSWORD='secret', SOURCE_AUTO_POSITION=1;","what":"Настраивает этот сервер как реплику, следующую за указанным источником с автопозиционированием на основе GTID.","when":"Чтобы настроить новую реплику или сменить источник репликации.","caution":"Это терминология MySQL 8.0.23+; в более старых версиях используется CHANGE MASTER TO ... MASTER_HOST (алиас в 8.0.23–8.3, удалён в 8.4). SOURCE_AUTO_POSITION=1 требует включённого GTID (gtid_mode=ON); иначе нужно указать SOURCE_LOG_FILE и SOURCE_LOG_POS. Перед выполнением команды репликация должна быть остановлена. Если пользователь репликации создан с caching_sha2_password (по умолчанию в 8.0+), подключение требует TLS: добавьте SOURCE_SSL=1 или, без TLS, задайте GET_SOURCE_PUBLIC_KEY=1; иначе поток I/O не сможет подключиться с ошибкой 2061.","since":"MySQL 8.0.23","note":"CHANGE REPLICATION SOURCE TO (SOURCE_HOST и т. д.) появился в MySQL 8.0.23; ранее использовался CHANGE MASTER TO ... MASTER_HOST / MASTER_AUTO_POSITION.","tags":["replication","change-source","replica","setup"]},{"id":"start-stop-replica","task":"Запустить / остановить реплику","category":"Репликация","command":"STOP REPLICA; START REPLICA;","what":"Останавливает и снова запускает потоки репликации I/O и SQL.","when":"Для обслуживания, изменения конфигурации или устранения ошибки репликации.","caution":"Это терминология MySQL 8.0.22+; в более старых версиях используется STOP SLAVE / START SLAVE. Только для потока SQL: STOP REPLICA SQL_THREAD; После STOP REPLICA можно выполнить CHANGE REPLICATION SOURCE.","since":"MySQL 8.0.22","note":"START REPLICA / STOP REPLICA появились в MySQL 8.0.22; ранее использовались START SLAVE / STOP SLAVE (старый синтаксис работает как алиас в 8.0.22–8.3 и удалён в 8.4).","tags":["replication","start","stop","replica"]},{"id":"replica-status","task":"Просмотреть состояние реплики (SHOW REPLICA STATUS)","category":"Репликация","command":"SHOW REPLICA STATUS\\G","what":"Подробно показывает подключение реплики к источнику, состояние потоков, отставание и ошибки.","when":"Чтобы проверить, исправна ли репликация и насколько она отстаёт.","caution":"Это терминология MySQL 8.0.22+; в более старых версиях используется SHOW SLAVE STATUS (алиас в 8.0.22–8.3, удалён в 8.4). Ключевые поля: Replica_IO_Running и Replica_SQL_Running должны быть 'Yes', Seconds_Behind_Source показывает отставание, Last_Error — ошибку.","since":"MySQL 8.0.22","note":"SHOW REPLICA STATUS появился в MySQL 8.0.22; ранее использовался SHOW SLAVE STATUS, а поля назывались Slave_IO_Running / Seconds_Behind_Master.","tags":["replication","status","replica","lag"]},{"id":"skip-gtid-transaction","task":"Пропустить сломанную транзакцию (GTID)","category":"Репликация","command":"STOP REPLICA; SET GTID_NEXT='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:15'; BEGIN; COMMIT; SET GTID_NEXT='AUTOMATIC'; START REPLICA;","what":"Фиксирует пустую транзакцию для указанного GTID, чтобы эта транзакция считалась 'применённой', и позволяет репликации продолжить работу.","when":"Когда дублирующее/неприменимое событие на реплике останавливает репликацию — чтобы пропустить это событие.","caution":"Пропуск транзакции создаёт расхождение данных между репликой и источником; используйте его, только если понимаете последствия пропускаемого события. Значение GTID возьмите из Last_Error / Retrieved_Gtid_Set в SHOW REPLICA STATUS. Если GTID выключен, используйте SET GLOBAL sql_replica_skip_counter = 1; (8.0.26+; ранее sql_slave_skip_counter, устаревший алиас в 8.0.26+).","tags":["replication","gtid","skip","recovery"]},{"id":"kill-query-connection","task":"Прервать запрос или подключение (KILL)","category":"Сессия","command":"KILL QUERY 4567;","what":"Прерывает запрос, выполняющийся в подключении с указанным ID, не закрывая подключение.","when":"Чтобы остановить долгий/зависший запрос.","caution":"Чтобы закрыть подключение (сессию) целиком: KILL CONNECTION 4567; (или просто KILL 4567). Если прервать выполняющуюся пишущую транзакцию, она откатывается, что на больших таблицах может занять много времени.","tags":["kill","query","connection","session"]},{"id":"process-list","task":"Просмотреть список процессов","category":"Сессия","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":"Выводит все не простаивающие подключения, выполняемый ими запрос и прошедшее время.","when":"Для диагностики активных сессий и долгих запросов.","caution":"Короткий вариант: SHOW FULL PROCESSLIST; performance_schema.processlist содержит все столбцы и поддерживает фильтрацию через WHERE. Столбец time — в секундах.","tags":["processlist","session","activity","performance-schema"]},{"id":"blocking-locks","task":"Просмотреть блокирующие блокировки (data_lock_waits)","category":"Сессия","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":"Показывает, какая транзакция какой блокируется, вместе с соответствующими запросами и ID потоков.","when":"Для диагностики ожиданий блокировок и замедлений, вызванных взаимоблокировками.","caution":"performance_schema.data_lock_waits появилось в MySQL 8.0; в более старых версиях используется information_schema.innodb_lock_waits. Чтобы остановить блокирующий поток, используйте KILL blocking_thread;.","since":"MySQL 8.0","note":"performance_schema.data_lock_waits появилось в MySQL 8.0; в MySQL 5.7 и ранее для той же информации использовалось information_schema.innodb_lock_waits.","tags":["blocking","lock","data-lock-waits","deadlock"]}]},"recipes/mysql-ext.json":{"engineId":"mysql","engine":"MySQL","order":2,"recipes":[{"id":"replica-applier-health","task":"Мониторинг отставания реплики и ошибок рабочих процессов applier (performance_schema)","category":"Репликация","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":"Раскладывает то, что SHOW REPLICA STATUS сводит в одну строку, по каналам и рабочим процессам: состояние потока-получателя, последняя ошибка каждого рабочего процесса, применённый / применяемый GTID и реальное отставание, вычисленное по исходной метке времени commit.","when":"Когда Seconds_Behind_Source выглядит недостоверно (скачет между 0 и большими значениями, NULL), чтобы найти, какой рабочий процесс завис на многопоточной реплике, и чтобы передавать в мониторинг точную метрику отставания.","caution":"current_lag_sec имеет смысл только во время применения транзакции; простаивающая реплика возвращает 0. Чтобы увидеть, отстаёт ли и получатель, сравните LAST_QUEUED_TRANSACTION с gtid_executed источника. Исходная метка времени commit берётся по часам источника; расхождение часов между серверами (NTP) искажает результат. Seconds_Behind_Source остаётся около нуля в начале большой транзакции и скачет в конце; долгие транзакции делают эту метрику вводящей в заблуждение.","note":"Столбцы ORIGINAL_COMMIT_TIMESTAMP / END_APPLY_TIMESTAMP появились в 8.0.2 и охватывают всю цепочку (источник → промежуточная → реплика); в 5.7 есть только Seconds_Behind_Master.","since":"MySQL 8.0","tags":["replication","lag","applier","worker","performance_schema","delay"]},{"id":"replica-parallel-applier","task":"Настроить многопоточную реплику (replica_parallel_workers)","category":"Репликация","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":"Задаёт число рабочих процессов, параллельно применяющих транзакции на реплике; replica_preserve_commit_order сохраняет порядок commit идентичным источнику. Отслеживание зависимостей WRITESET на источнике позволяет параллельно применять транзакции, затрагивающие разные строки, даже если на источнике они фиксировались последовательно.","when":"Когда реплика не успевает за скоростью записи источника и накапливается отставание, а applier фактически работает в один поток.","caution":"Новое число рабочих процессов вступает в силу при перезапуске applier. Больше рабочих процессов, чем ядер CPU, — это лишние накладные расходы на координацию; начните с диапазона 8–16 и следите за отставанием и ошибками рабочих процессов. Одну огромную транзакцию или интенсивный DML на таблице без PK распараллелить нельзя, и обычно именно это — настоящая причина устойчивого отставания. replica_preserve_commit_order=OFF даёт больше параллелизма, но допускает кратковременное нарушение порядка commit на реплике; на репликах, обслуживающих чтение, держите ON.","note":"С 8.0.27 значения по умолчанию: replica_parallel_workers=4, replica_parallel_type=LOGICAL_CLOCK и replica_preserve_commit_order=ON. До 8.0.26 переменные назывались slave_parallel_workers / slave_preserve_commit_order. binlog_transaction_dependency_tracking объявлен устаревшим в 8.0.35 и удалён в 8.4, где поведение WRITESET используется по умолчанию.","since":"MySQL 8.0.26","tags":["replication","parallel","replica_parallel_workers","writeset","lag","mta"]},{"id":"replica-fix-row-conflict","task":"Починить сломанную реплику: конфликты строк 1062 / 1032","category":"Репликация","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":"Исправляет два самых частых сбоя row-based репликации (1062 duplicate key, 1032 row not found) без пропуска транзакции, приводя строку на реплике к состоянию, которое ожидает источник; согласованность данных сохраняется.","when":"Когда Replica_SQL_Running = No с ошибкой 1062 / 1032; как корректное исправление вместо пропуска GTID пустой транзакцией.","caution":"Исправление на реплике нужно выполнять с sql_log_bin=0; иначе создаётся errant GTID, который даст о себе знать при failover. Если super_read_only=ON, временно отключите его и затем включите снова. Значения before-image предполагают binlog_row_image=FULL; при MINIMAL журналируется только PK, а остальную часть строки нужно прочитать с источника. Один конфликт в таблице обычно означает, что их больше; после исправления сравните таблицу с источником через pt-table-checksum. Путь к relay log: SELECT @@relay_log_basename;","note":"replica_exec_mode=IDEMPOTENT молча подавляет эти ошибки; он предназначен для NDB и особых случаев и не должен оставаться включённым постоянно. Если транзакцию действительно нужно пропустить, см. рецепт skip-gtid-transaction.","since":"MySQL 8.0.22","tags":["replication","1062","1032","mysqlbinlog","relay log","broken replica"]},{"id":"gtid-errant-transactions","task":"Найти и нейтрализовать errant GTID-транзакции","category":"Репликация","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":"Выявляет через разность наборов GTID транзакции, выполненные напрямую на реплике и не имеющие соответствия на источнике (errant), и выравнивает наборы GTID по всей топологии, помечая их пустыми транзакциями на источнике.","when":"Как регулярная проверка перед failover / switchover; когда Orchestrator или MySQL Shell отказывается повышать реплику из-за errant GTID или реплика при подключении к новому источнику падает с ошибкой 1236.","caution":"Сначала выясните по binlog реплики, что сделала errant-транзакция (mysqlbinlog --include-gtids=...). Если она изменила данные, пустая транзакция лишь маскирует GTID, а расхождение данных остаётся; пересоздайте реплику (CLONE) или выровняйте данные вручную. Пустые транзакции, зафиксированные на источнике, распространяются на все реплики; так и задумано. Держите super_read_only=ON на репликах, чтобы предотвратить повторение.","note":"После повышения реплики остальные реплики запрашивают у неё эти GTID; если соответствующий binlog удалён, репликация ломается с ошибкой 1236. Именно поэтому errant GTID — риск, остающийся невидимым до failover.","since":"MySQL 5.6","tags":["gtid","errant","gtid_subtract","failover","replication","empty transaction"]},{"id":"replica-rebuild-clone","task":"Пересоздать реплику с нуля через плагин CLONE","category":"Репликация","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":"Физически копирует данные InnoDB донора по сети, перезапускает получателя и переносит состояние GTID; создаёт согласованную реплику без отдельных шагов бэкапа, передачи и восстановления.","when":"Чтобы пересоздать повреждённую реплику, реплику с errant GTID или настолько отставшую, что её binlog уже удалены, либо чтобы добавить новую реплику.","caution":"Все данные получателя (включая пользователей) заменяются копией донора; пароль root становится паролем донора. Донору и получателю нужны одинаковые серия релизов, платформа и набор плагинов (до 8.0.37 требовался точно такой же релиз). Пользователю на получателе, выполняющему команду, нужен CLONE_ADMIN. По завершении клонирования mysqld перезапускается сам; без супервизора, такого как systemd / mysqld_safe, возвращается 3707 'Restart server failed (mysqld is not managed by supervisor process)'. Это не сбой: клонирование завершено, запустите сервер вручную. Донор испытывает высокую нагрузку IO / сети; ограничьте её через clone_max_concurrency и clone_max_data_bandwidth. При выключенном GTID используйте BINLOG_FILE / BINLOG_POSITION из clone_status как SOURCE_LOG_FILE / SOURCE_LOG_POS.","note":"Клонируются только данные InnoDB; таблицы MyISAM и CSV приходят пустыми. В InnoDB Cluster addInstance(..., {recoveryMethod: 'clone'}) использует тот же механизм.","since":"MySQL 8.0.17","tags":["clone","rebuild","replica","provisioning","gtid","reseed"]},{"id":"delayed-replica-recover","task":"Восстановиться после ошибочного DROP / DELETE с помощью отложенной реплики (SOURCE_DELAY + UNTIL)","category":"Репликация","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":"Заставляет реплику применять события с фиксированной задержкой; когда на источник приходит ошибочный DROP / DELETE, реплика прокручивается вперёд до момента непосредственно перед этой транзакцией, и потерянные данные восстанавливаются из неё.","when":"Как слой быстрого восстановления от человеческих ошибок на больших базах, где восстановление из бэкапа заняло бы часы.","caution":"Когда coordinator в состоянии OFF, данные находятся в состоянии непосредственно перед ошибочной операцией; выгрузите затронутую таблицу через mysqldump и загрузите её в источник. Затем верните реплику в отложенный режим; если она применит и ошибочную транзакцию, данные будут утеряны и здесь. Отложенная реплика не должна быть кандидатом на failover (исключите её в Orchestrator / Router / ProxySQL). Relay log накапливаются за всё время задержки; рассчитайте размер диска соответственно.","note":"До 8.0.23 используйте CHANGE MASTER TO MASTER_DELAY. Задержка отсчитывается от commit на непосредственном источнике, поэтому задержки промежуточных реплик в цепочке суммируются. SQL_BEFORE_GTIDS останавливается перед указанным GTID, SQL_AFTER_GTIDS применяет и его; до 8.2 (включая 8.0) использование SQL_AFTER_GTIDS с многопоточным applier выдаёт предупреждение, и реплика переключается в однопоточный режим.","since":"MySQL 8.0.23","tags":["delayed replica","source_delay","until","sql_before_gtids","recovery","drop table"]},{"id":"mysqldump-consistent","task":"Снять согласованный логический бэкап с информацией GTID через mysqldump","category":"Бэкап и 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":"Выгружает таблицы InnoDB в рамках одного согласованного снимка и записывает в начало файл / позицию binlog (в виде комментария) и оператор SET @@GLOBAL.GTID_PURGED, чтобы дамп можно было использовать для создания реплики или как начальную точку PITR на основе binlog.","when":"Для баз размером до нескольких десятков GB, переноса схем / таблиц и логической миграции между версиями.","caution":"--single-transaction согласован только для InnoDB; таблицы MyISAM выгружаются несогласованно. DDL (ALTER / RENAME / TRUNCATE) на таблице во время дампа приводит к сбою дампа или пропуску таблицы. --source-data / --set-gtid-purged в начале берут кратковременную глобальную блокировку чтения (требуется привилегия RELOAD); если в этот момент выполняется долгий запрос, вся запись замирает, пока блокировка ожидает. Частичный дамп (--databases) всё равно записывает все GTID; при переносе подмножества на другой сервер используйте --set-gtid-purged=OFF. Дамп однопоточный; для больших объёмов предпочтительнее MySQL Shell dump или XtraBackup.","note":"--source-data заменил --master-data в 8.0.26. mysqlpump объявлен устаревшим в 8.0.34 и удалён в 8.4; для параллельных логических бэкапов используйте MySQL Shell util.dumpInstance.","since":"MySQL 8.0.26","tags":["mysqldump","backup","single-transaction","gtid","logical backup","dump"]},{"id":"mysqlsh-dump-load","task":"Параллельные выгрузка / загрузка через MySQL Shell (util.dumpInstance, loadDump)","category":"Бэкап и 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":"Делит таблицы на фрагменты и выгружает / загружает их во много потоков; и дамп, и восстановление в разы быстрее mysqldump. Каталог дампа хранит координаты GTID и binlog в @.json.","when":"Для логических бэкапов объёмом в сотни GB, логической миграции при обновлениях и переносов on-prem → cloud (RDS, HeatWave).","caution":"--consistent=true берёт кратковременную глобальную блокировку чтения / backup lock; если выполняется долгий запрос, запись ожидает. loadDump использует LOAD DATA LOCAL, поэтому на целевой стороне нужен local_infile=ON, который затем следует отключить. Прерванная загрузка продолжается с места остановки (файл прогресса); чтобы начать заново, используйте --resetProgress. Учётные записи пользователей по умолчанию не загружаются (--loadUsers=true). --skipBinlog=true отключает запись в binlog во время загрузки; если у целевого сервера есть реплики, оставьте false.","note":"Это не физический бэкап; для PITR применяйте binlog начиная со значения gtidExecuted из @.json дампа. Опции ocimds / compatibility создают дампы, соблюдающие ограничения HeatWave / RDS.","since":"MySQL Shell 8.0.21","tags":["mysqlsh","dumpinstance","loaddump","parallel","logical backup","migration"]},{"id":"xtrabackup-full-incremental","task":"Полный + инкрементальный физический бэкап и восстановление через XtraBackup","category":"Бэкап и 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":"Снимает горячий физический бэкап, копируя файлы InnoDB и redo log во время работы сервера; инкрементальный бэкап содержит только страницы, изменённые с предыдущего бэкапа. Шаг prepare применяет redo и делает бэкап согласованным.","when":"Для больших баз, которым нужно быстрое восстановление (в разы быстрее логического), для создания реплик и для стратегий ежедневный full + ежечасный incremental.","caution":"Версия XtraBackup должна соответствовать серии релизов сервера (сервер 8.0 → XtraBackup 8.0, 8.4 → 8.4); если сервер использует более новый формат redo / словаря данных, чем сборка XtraBackup, бэкап будет отклонён. Неподготовленный (без prepare) бэкап несогласован. Если забыть --apply-log-only на промежуточных шагах, последующие инкрементальные бэкапы станут непригодными. Для восстановления datadir должен быть пустым; --move-back экономит диск, но расходует бэкап. Пользователь для бэкапа: BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT и SELECT на performance_schema.log_status, keyring_component_status, replication_group_members.","note":"xtrabackup_binlog_info содержит файл / позицию binlog и набор GTID бэкапа; PITR воспроизводит binlog с этой точки. Для потоковой передачи на другой хост используйте --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":"Восстановление на момент времени по binlog: найти и пропустить ошибочную транзакцию","category":"Бэкап и 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":"После восстановления бэкапа по порядку воспроизводит binlog, чтобы довести базу до нужной точки. При включённом GTID транзакции, уже содержащиеся в бэкапе, пропускаются автоматически; --exclude-gtids убирает только ошибочную операцию и сохраняет все изменения после неё.","when":"После ошибочного DROP / DELETE / UPDATE — чтобы восстановиться, не потеряв изменения, сделанные между бэкапом и инцидентом.","caution":"Восстанавливайте на отдельном сервере, а не в production; проверьте, затем перенесите только затронутую таблицу. --start/--stop-datetime интерпретируются в часовом поясе машины, на которой запущен mysqlbinlog. --stop-position применяется только к последнему указанному файлу; при нескольких файлах воспроизведите предыдущие файлы полностью отдельной командой. --exclude-gtids безопасен только для независимой операции; если последующие транзакции пишут в удалённую таблицу, они тоже завершатся ошибкой. Нужные binlog не должны быть удалены на источнике; храните binlog дольше, чем как минимум один интервал между бэкапами.","note":"Запускайте mysqlbinlog и mysql в одном конвейере со всеми файлами в одной команде; воспроизведение файлов по одному ломает временные таблицы и транзакции, охватывающие несколько файлов. Для очень больших наборов binlog альтернатива — разместить binlog как relay log на сервере восстановления и применить их (параллельным) потоком SQL.","since":"MySQL 8.0","tags":["pitr","mysqlbinlog","binlog","point in time","exclude-gtids","recovery"]},{"id":"innodb-cluster-status","task":"Проверить работоспособность InnoDB Cluster / Group Replication","category":"InnoDB Cluster","command":"# Shell: topology, primary, fault tolerance, lag\nmysqlsh icadmin@db1:3306 -- cluster status --extended=1\nmysqlsh icadmin@db1:3306 -- cluster list-routers\n\n-- SQL: group members and queues from any member\nSELECT m.MEMBER_HOST, m.MEMBER_PORT, m.MEMBER_STATE, m.MEMBER_ROLE, m.MEMBER_VERSION,\n       s.COUNT_TRANSACTIONS_IN_QUEUE AS certify_queue,\n       s.COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE AS apply_queue,\n       s.COUNT_CONFLICTS_DETECTED AS conflicts\nFROM performance_schema.replication_group_members m\nJOIN performance_schema.replication_group_member_stats s ON s.MEMBER_ID = m.MEMBER_ID\nORDER BY m.MEMBER_ROLE, m.MEMBER_HOST;","what":"Показывает общее состояние кластера (OK / OK_NO_TOLERANCE / NO_QUORUM), primary, состояние каждого участника и очереди certification / applier; очереди показывают, какой участник отстаёт, и риск flow control.","when":"До и после каждого вмешательства; при алертах, замедлении записи или когда участник показывает RECOVERING / ERROR / UNREACHABLE.","caution":"statusText должен сообщать 'can tolerate up to ONE failure' (3 участника). При NO_QUORUM запись останавливается; используйте forceQuorumUsingPartitionOf, только если уверены, что другой раздел неработоспособен, иначе получите split-brain. Участник, у которого apply_queue продолжает расти, запускает flow control и замедляет запись на primary. Долгое состояние RECOVERING означает, что выполняется clone / инкрементальное восстановление; при ERROR проверьте журнал ошибок и попробуйте cluster.rejoinInstance(). Специальные символы в паролях (#, @) в URI нужно кодировать (# → %23).","note":"MEMBER_COMMUNICATION_STACK появился в 8.0.27; стек MYSQL передаёт трафик GR через 3306 с обычными TLS и аутентификацией пользователей (XCOM использует отдельный порт, обычно 33061). В 8.4 group_replication_consistency по умолчанию равен BEFORE_ON_PRIMARY_FAILOVER, а exit_state_action — OFFLINE_MODE.","since":"MySQL 8.0","tags":["innodb cluster","group replication","mysqlsh","status","flow control","quorum"]},{"id":"innodb-cluster-switch-primary","task":"Плановое переключение primary в 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":"Контролируемо переносит роль primary на другого участника в режиме single-primary; старый primary становится super_read_only, новый применяет накопившееся и открывается для записи. MySQL Router узнаёт о новом primary автоматически.","when":"Перед обслуживанием, патчингом ОС или обновлением primary и при переносе оборудования.","caution":"Открытые транзакции на старом primary прерываются по истечении тайм-аута; без тайм-аута переключение ждёт их и занимает больше времени. Если приложение подключается не через Router, перенаправить его нужно самостоятельно. При BEFORE_ON_PRIMARY_FAILOVER запись ждёт, пока новый primary не применит накопившееся; сначала убедитесь в cluster status, что apply_queue пуста. В InnoDB Cluster предпочтительнее команда Shell; она остаётся согласованной с метаданными.","note":"group_replication_set_as_primary появилась в 8.0.13; параметр timeout добавлен в 8.0.29. При внеплановом failover GR сам выбирает новый primary (group_replication_member_weight задаёт предпочтение).","since":"MySQL 8.0.13","tags":["innodb cluster","switchover","primary","group replication","set_as_primary","maintenance"]},{"id":"innodb-cluster-reboot-outage","task":"Восстановить кластер после полного отключения (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":"Заново выполняет bootstrap группы после потери питания или одновременного перезапуска всех узлов, когда кворума нет; выбирает участника с наибольшим набором GTID и заново подключает остальных.","when":"Когда cluster status сообщает 'Cluster is not reachable' и все участники показывают OFFLINE.","caution":"Используйте, только когда все участники в состоянии OFFLINE; если хотя бы один участник ещё ONLINE, группа жива, и остальные подключаются через rejoinInstance. Перед запуском запустите все узлы: если недоступный участник более актуален, его транзакции будут потеряны. force: true игнорирует отсутствующих участников; это крайняя мера. Опция primary: 'host:port' принудительно назначает определённого участника primary.","note":"Опции dryRun, primary и force появились в Shell 8.0.31; более ранние версии работали через интерактивные запросы. Даже при group_replication_start_on_boot=ON после полного отключения группа сама не формируется; bootstrap нужно выполнять осознанно из одного места.","since":"MySQL Shell 8.0.31","tags":["innodb cluster","outage","reboot","bootstrap","mysqlsh","disaster"]},{"id":"mysql-router-bootstrap","task":"Выполнить bootstrap MySQL Router для InnoDB Cluster","category":"Router и прокси","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":"Регистрирует Router в метаданных кластера и генерирует учётную запись и конфигурацию; приложение обращается к текущему primary через порт RW, а к secondary — по кругу (round-robin) через порт RO.","when":"При размещении прозрачного слоя failover перед InnoDB Cluster / ClusterSet / ReplicaSet; обычно по одному Router на сервер приложений.","caution":"Router не хранит состояние; размещение рядом с приложением исключает единую точку отказа, а один центральный Router — нет. Учётная запись bootstrap используется только при bootstrap; Router читает метаданные своей учётной записью (routeruser). Об изменениях топологии он узнаёт через кэш метаданных без повторного bootstrap. Порт с разделением RW (6450) отправляет чтения внутри транзакции на primary, а остальные — на secondary; будьте осторожны с приложениями, ожидающими read-your-writes. Версия Router не должна быть старше серверов MySQL в кластере.","note":"Порт с разделением чтения/записи (6450) появился в 8.2. При failover существующие подключения разрываются; у приложения должна быть логика переподключения (retry).","since":"MySQL Router 8.0","tags":["mysql router","bootstrap","innodb cluster","failover","read write split","6446"]},{"id":"proxysql-rw-split","task":"Разделение чтения/записи и проверки работоспособности реплик через ProxySQL","category":"Router и прокси","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":"Автоматически управляет hostgroup записи (10) и чтения (20) на основе флага read_only; направляет SELECT на реплики, а блокирующие чтения и запись — на источник; временно исключает реплику, превышающую max_replication_lag (SHUNNED).","when":"Чтобы распределить нагрузку чтения в топологии асинхронной репликации, не трогая приложение; для пула подключений и маршрутизации / кэширования / файрвола на уровне запросов.","caution":"Конфигурация имеет 3 уровня (MEMORY → RUNTIME → DISK): без LOAD ... TO RUNTIME ничего не меняется, без SAVE ... TO DISK всё теряется при перезапуске. Проверка read_only требует super_read_only=ON на репликах; иначе реплики переносятся в hostgroup записи. Отправка всех SELECT на реплики вызывает ошибки read-after-write из-за отставания; безопаснее писать правила только для тяжёлых / отчётных запросов на основе stats_mysql_query_digest. Интерфейс администратора — SQLite: функций MySQL, таких как LEFT(), нет, используйте substr(). Смените учётную запись по умолчанию admin:admin (admin-admin_credentials).","note":"Проверка max_replication_lag требует REPLICATION CLIENT для пользователя мониторинга. Для Group Replication / InnoDB Cluster используйте таблицу mysql_group_replication_hostgroups.","since":"ProxySQL 2.0","tags":["proxysql","read write split","hostgroup","query rules","connection pool","replication lag"]},{"id":"online-ddl-instant-inplace","task":"Online DDL: ALGORITHM=INSTANT / INPLACE, LOCK=NONE и отслеживание хода выполнения","category":"Онлайн-операции","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 меняет только словарь данных и завершается мгновенно независимо от размера таблицы. INPLACE + LOCK=NONE обрабатывает таблицу на месте, допуская параллельные чтение и запись. Последний запрос показывает, на каком этапе находится выполняющийся ALTER, и процент выполнения.","when":"Перед изменением схемы production-таблиц; чтобы знать, какие операции можно выполнить без простоя, и оценить, когда завершится долгий ALTER.","caution":"INSTANT и INPLACE всё равно берут кратковременную эксклюзивную блокировку метаданных в начале и в конце; если долгая транзакция удерживает таблицу, ALTER ждёт, и каждый запрос за ним встаёт в очередь (см. диагностику metadata lock). Снизьте lock_wait_timeout в сессии (SET SESSION lock_wait_timeout = 5). DML, поступающий во время INPLACE, журналируется в пределах innodb_online_alter_log_max_size; при превышении ALTER в конце откатывается. Изменение типа столбца, преобразование charset и изменение PK требуют COPY и блокируют запись; на больших таблицах используйте gh-ost / pt-online-schema-change. На реплике ALTER выполняется в один поток столько же времени; большой INPLACE ALTER вызывает отставание реплики, INSTANT — нет.","note":"INSTANT ADD COLUMN появился в 8.0.12 только для последнего столбца; с 8.0.29 столбцы можно добавлять в любую позицию и поддерживается INSTANT DROP COLUMN. Расширение VARCHAR затрагивает только метаданные, если число байтов длины не меняется (граница 255 байт не пересекается); оно завершается мгновенно с INPLACE, а не INSTANT. Индексированный столбец нельзя удалить через INSTANT; сначала удалите индекс.","since":"MySQL 8.0.29","tags":["online ddl","instant","inplace","alter table","lock=none","progress"]},{"id":"pt-online-schema-change","task":"Неблокирующий ALTER большой таблицы через pt-online-schema-change","category":"Онлайн-операции","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":"Создаёт пустую копию с новой структурой, копирует строки порциями, отражает изменения во время копирования через триггеры и в конце меняет таблицы местами атомарным RENAME. Самостоятельно замедляется / приостанавливается, когда нагрузка или отставание реплики превышают пороги.","when":"Для ALTER, требующих COPY (тип столбца, charset, изменение PK) или способных вызвать долгое отставание реплики.","caution":"У таблицы должен быть PK или уникальный ключ. Триггеры добавляют накладные расходы к каждой записи во время миграции; на таблицах с очень интенсивной записью gh-ost влияет меньше. Если дочерние таблицы ссылаются на эту таблицу через внешние ключи, требуется --alter-foreign-keys-method=auto. Для учётных записей caching_sha2_password добавьте s=1 (TLS) в DSN; иначе подключение к реплике завершится ошибкой 'Authentication requires secure connection'. DSN для --check-replica-lag не наследует пользователя / пароль из основного DSN; укажите их. Обмен таблиц берёт кратковременную блокировку метаданных; держите lock_wait_timeout низким — при невозможности получить блокировку выполняется повторная попытка.","note":"Реплики по умолчанию обнаруживаются через processlist по IP; если эти IP недоступны, используйте таблицу DSN с --recursion-method=dsn=D=percona,t=dsns. В 3.7 --check-slave-lag переименован в --check-replica-lag (старое имя объявлено устаревшим).","since":"Percona Toolkit 3.7","tags":["pt-online-schema-change","pt-osc","percona toolkit","alter","trigger","online schema change"]},{"id":"gh-ost-migration","task":"Онлайн-изменение схемы без триггеров через gh-ost","category":"Онлайн-операции","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":"Строит ghost-таблицу и копирует строки; применяет параллельные изменения, читая binlog вместо триггеров. Throttling, пауза и момент cut-over управляются во время работы через сокет или флаг-файлы.","when":"На больших таблицах с интенсивной записью, где накладные расходы триггеров неприемлемы и нужно самостоятельно выбрать момент cut-over (например, окно с низким трафиком).","caution":"Требует binlog_format=ROW и binlog_row_image=FULL; реплике, к которой он подключается, нужен log_replica_updates=ON. Пользователю нужны SUPER или REPLICATION CLIENT / REPLICATION SLAVE плюс ALL на таблицу. Таблицы с внешними ключами (родительские или дочерние) не поддерживаются. После cut-over старая таблица остаётся как _<table>_del; удалите её вручную после проверки (--ok-to-drop-table удаляет её автоматически). Релизы gh-ost до 1.1.8 используют SHOW SLAVE STATUS и останавливаются с ошибкой 1064 на MySQL 8.4; поддержка 8.4 появилась в 1.1.8. gh-ost сам создаёт файл, указанный в --postpone-cut-over-flag-file; cut-over не выполняется, пока файл не удалён.","note":"Опции --test-on-replica и --migrate-on-replica позволяют сначала опробовать и проверить изменение только на реплике. Причина троттлинга (throttle) и ETA выводятся командой 'status'.","tags":["gh-ost","online schema change","binlog","cut-over","alter","triggerless"]},{"id":"ps-top-digests","task":"Поиск самых дорогих запросов (performance_schema digest)","category":"Производительность","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":"Ранжирует нормализованные запросы по суммарному времени: число вызовов, средняя и p95-задержка, доля от общего времени, прочитанные / отправленные строки на вызов, выполнения без индекса и временные таблицы, сброшенные на диск. Второй запрос показывает, на какие ожидания (IO, блокировки, redo) уходит время.","when":"Первый шаг при росте нагрузки на CPU / IO или жалобах пользователей «база тормозит»; работает даже при выключенном slow log.","caution":"Значения накапливаются с последнего перезапуска или TRUNCATE TABLE performance_schema.events_statements_summary_by_digest; снимите два снимка и сравните их, чтобы увидеть текущую активность. Таблица ограничена performance_schema_digests_size строками; после заполнения новые запросы агрегируются в одну строку с DIGEST = NULL. DIGEST_TEXT обрезается до performance_schema_max_digest_length (1024) байт. Время указано в пикосекундах. Высокое соотношение rows_exam_per_call / rows_sent_per_call — самый явный признак отсутствующего индекса.","note":"Столбцы QUANTILE_95 / QUANTILE_99 и QUERY_SAMPLE_TEXT появились в 8.0. Те же данные в читаемых единицах доступны в представлениях sys.statement_analysis и sys.statements_with_full_table_scans.","since":"MySQL 8.0","tags":["performance_schema","digest","top sql","slow query","sys","wait"]},{"id":"sys-session-activity","task":"Что выполняется прямо сейчас: активные сессии, задержки, блокировки и транзакции (sys.session)","category":"Производительность","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":"Дополняет SHOW PROCESSLIST данными performance_schema: задержка выполняемой команды, процент выполнения, время ожидания блокировки, прочитанные строки, полное сканирование / временная таблица на диске и состояние открытой транзакции — в одной строке. Также показывает сессии в Sleep, удерживающие открытую транзакцию.","when":"В первые 30 секунд инцидента: какой запрос сколько выполняется, кто ждёт блокировок, какая простаивающая сессия держит открытую транзакцию.","caution":"sys.session выполняет несколько соединений по performance_schema; при тысячах подключений сам запрос может занимать секунды, поэтому для частого мониторинга используйте представления x$ и LIMIT. progress заполняется только при включённых stage-инструментах и для операций, сообщающих о ходе выполнения, например ALTER. Сессия в Sleep с trx_state=ACTIVE — транзакция, забытая приложением / пулом соединений с autocommit=0; это самая частая причина проблем с metadata lock и purge.","note":"sys.processlist возвращает те же данные, включая фоновые потоки. Завершение: KILL QUERY conn_id (только команда) или KILL conn_id (соединение, транзакция откатывается).","since":"MySQL 8.0","tags":["sys.session","processlist","active sessions","incident","trx","session"]},{"id":"metadata-lock-diagnose","task":"Диагностика 'Waiting for table metadata lock' и поиск настоящего блокировщика","category":"Производительность","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":"Показывает, metadata lock какой сессии ждёт DDL (ALTER / TRUNCATE / RENAME), и запросы, выстроившиеся в очередь за DDL. Блокировщиком обычно оказывается сессия в Sleep, которая прочитала таблицу и не закрыла транзакцию.","when":"Когда после запуска ALTER все запросы к этой таблице скапливаются в 'Waiting for table metadata lock'.","caution":"Ожидающий DDL блокирует и SELECT, пришедшие после него; во время инцидента решайте быстро: KILL блокирующей сессии или отмена ALTER (KILL QUERY). PROCESSLIST_INFO блокировщика может быть NULL (простой); последнюю команду смотрите в performance_schema.events_statements_history или sys.session.last_statement. Профилактика: выполняйте DDL после SET SESSION lock_wait_timeout = 5, чтобы ALTER быстро сдавался, и избегайте долгих открытых транзакций в приложении.","note":"Инструмент wait/lock/metadata/sql/mdl в 8.0 включён по умолчанию; в 5.7 его нужно включить в setup_instruments. sys.schema_table_lock_waits показывает блокировку SHARED_UPGRADABLE самого ALTER как блокировщика; настоящий виновник — сессия с 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":"Анализ взаимоблокировок InnoDB и запись всех их в журнал","category":"Производительность","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":"Показывает две транзакции во взаимоблокировке, блокировку, которую каждая удерживает и ждёт (индекс, lock_mode, запись), и какую из них InnoDB откатил; счётчик отражает частоту, а журнал ошибок хранит прошлые взаимоблокировки.","when":"Когда приложение получает ошибку 1213 'Deadlock found when trying to get lock' или растёт число оповещений о взаимоблокировках.","caution":"LATEST DETECTED DEADLOCK хранит только последнее событие и теряется при перезапуске. Имя индекса и lock_mode (X, S, gap, insert intention) в строках 'HOLDS THE LOCK(S)' / 'WAITING FOR THIS LOCK TO BE GRANTED' объясняют причину; 'WE ROLL BACK TRANSACTION (n)' указывает жертву. При 1213 приложение должно повторить всю транзакцию целиком. Постоянные решения: обращаться к строкам в одном порядке, делать транзакции короткими, использовать правильный индекс для уменьшения gap-блокировок и READ COMMITTED там, где это уместно. log_error_verbosity=3 включает также все сообщения Note; следите за ростом журнала ошибок. Переменной состояния Innodb_deadlocks в MySQL Community нет; для счётчика используйте INNODB_METRICS.","note":"При очень высокой конкурентности само обнаружение взаимоблокировок может стать узким местом по CPU; innodb_deadlock_detect=OFF рассматривают только вместе с низким innodb_lock_wait_timeout. Таймаут ожидания блокировки (1205) — не взаимоблокировка; для поиска блокировщика используйте рецепт blocking-locks.","since":"MySQL 8.0","tags":["deadlock","innodb","1213","innodb status","lock","error log"]},{"id":"long-trx-history-list","task":"Долгие транзакции и history list length (отставание purge)","category":"Производительность","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":"Показывает, насколько отстаёт purge (history list length), и самые старые транзакции, которые его сдерживают: владелец, возраст, изменённые строки и последний выполненный SQL.","when":"Когда растёт undo-табличное пространство, когда одни и те же запросы со временем замедляются (сканируют старые версии строк) или при оповещениях о месте на диске.","caution":"Самая частая причина — сессия в Sleep с открытой транзакцией или забытый consistent snapshot под REPEATABLE READ (например, брошенный mysqldump / отчёт). После KILL откат длится пропорционально числу изменённых транзакцией строк; завершение транзакции с большим trx_rows_modified означает долгий откат с интенсивным IO, и перезапуск его не сокращает. Undo усекается автоматически, когда purge догоняет (innodb_undo_log_truncate=ON).","note":"performance_schema.processlist появился в 8.0.22 и заменяет устаревший information_schema.PROCESSLIST. То же значение даёт 'History list length' в SHOW ENGINE INNODB STATUS.","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":"Короткий полный slow log и его анализ с pt-query-digest","category":"Производительность","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":"Фиксирует все запросы за короткое окно с long_query_time=0; pt-query-digest группирует их по отпечатку (fingerprint) и строит ранжированный отчёт с суммарным / средним / p95-временем, прочитанными строками и примером запроса.","when":"Когда дайджестов performance_schema недостаточно: анализ с полным текстом запросов и реальными параметрами, сравнение до / после деплоя, офлайн-разбор на другом хосте.","caution":"long_query_time копируется из глобального значения при старте сессии; существующие соединения пула не видят новый порог до переподключения. long_query_time=0 на нагруженном сервере пишет тысячи записей в секунду; следите за местом на диске и IO, держите окно коротким и обязательно возвращайте значение. Для анализа копируйте файл с продакшена. Пользователю, запускающему pt-query-digest, нужен доступ на чтение файла (режим 640, mysql:mysql).","note":"log_slow_extra (8.0.14) записывает дополнительные поля, такие как Bytes_sent, Read_first / Read_next и Created_tmp_disk_tables; pt-query-digest показывает их в отчёте. Постоянное включение / выключение — в рецепте enable-general-slow-log.","since":"MySQL 8.0.14","tags":["slow log","pt-query-digest","long_query_time","percona toolkit","query analysis","slow query"]},{"id":"explain-analyze-optimizer-trace","task":"Отладка плохого плана с EXPLAIN ANALYZE и optimizer trace","category":"Производительность","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 показывает для каждого шага плана оценочные (cost, rows) и фактические (actual time, rows, loops) значения рядом; optimizer trace показывает рассмотренные пути доступа, их стоимость и почему каждый был выбран или отвергнут.","when":"Когда запрос внезапно замедлился (смена плана), оптимизатор выбирает не тот индекс, или нужно найти источник расхождения оценочного и фактического числа строк.","caution":"EXPLAIN ANALYZE реально выполняет запрос; осторожно с тяжёлыми запросами на продакшене. Работает с SELECT, TABLE и многотабличными UPDATE / DELETE. Расхождение оценочных и фактических строк более чем в 10 раз указывает на проблему статистики / гистограмм (ANALYZE TABLE, гистограммы). На внутренней стороне nested loop actual rows указывается на один цикл; итого = rows × loops. Если MISSING_BYTES_BEYOND_MAX_MEM_SIZE > 0, трассировка обрезана; увеличьте optimizer_trace_max_mem_size. Трассировка хранит только последнюю команду той же сессии.","note":"EXPLAIN FORMAT=TREE появился в 8.0.16, EXPLAIN ANALYZE — в 8.0.18. Для фиксации плана подсказки оптимизатора (/*+ INDEX(t ix) */, JOIN_ORDER) дают более точный контроль, чем FORCE INDEX.","since":"MySQL 8.0.18","tags":["explain analyze","optimizer trace","execution plan","query tuning","index","plan"]},{"id":"histogram-statistics","task":"Создание и управление гистограммами для неиндексированных столбцов","category":"Индексы и статистика","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":"Сохраняет распределение значений столбца (singleton или equi-height) в словаре данных; оптимизатор правильно оценивает селективность фильтров по неиндексированным столбцам, что улучшает порядок соединений и качество плана.","when":"Когда EXPLAIN ANALYZE показывает большое расхождение оценочных и фактических строк на столбцах с перекосом (например, status, type), где добавлять индекс нежелательно.","caution":"Для индексированных столбцов оптимизатор использует index dives, поэтому гистограмма обычно не влияет (если не превышен eq_range_index_dive_limit). В режиме MANUAL по умолчанию гистограмма не обновляется при изменении данных; запланируйте ANALYZE ... UPDATE HISTOGRAM. Большие таблицы сэмплируются в соответствии с histogram_generation_max_mem_size (sampling-rate < 1). ANALYZE TABLE берёт кратковременный metadata lock; обновление гистограммы не меняет статистику таблицы (кардинальность индексов).","note":"Гистограммы появились в 8.0; опция AUTO UPDATE добавлена в 8.4 и обновляет гистограмму вместе с автоматическим пересчётом статистики. Максимум — 1024 корзины (buckets); если различных значений меньше, чем корзин, создаётся singleton-гистограмма.","since":"MySQL 8.0","tags":["histogram","statistics","optimizer","analyze table","selectivity","cardinality"]},{"id":"invisible-redundant-index","task":"Поиск неиспользуемых / избыточных индексов, перевод в invisible, безопасное удаление","category":"Индексы и статистика","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":"Выводит неиспользуемые индексы и индексы, перекрытые другим индексом; если сделать индекс невидимым перед удалением, можно проверить поведение продакшена без него и при необходимости мгновенно вернуть (без перестроения).","when":"При чистке индексов для снижения стоимости записи, давления на buffer pool и занимаемого места на диске.","caution":"schema_unused_indexes опирается только на статистику, собранную с последнего перезапуска; редкие запросы, например ежемесячные отчёты, могут не попасть. Если реплики обслуживают разную нагрузку, проверяйте каждый сервер отдельно. Невидимый индекс по-прежнему поддерживается при DML (стоимость записи сохраняется), но оптимизатор его игнорирует; уникальный индекс в невидимом состоянии всё равно обеспечивает уникальность. PRIMARY KEY нельзя сделать невидимым. Запросы, указывающие индекс в FORCE INDEX / USE INDEX, падают с ошибкой, когда он невидим.","note":"Переключение видимости индекса затрагивает только метаданные и выполняется мгновенно. Список избыточных строится по порядку столбцов; индекс (a) перекрывается (a,b), а (b) — нет.","since":"MySQL 8.0","tags":["invisible index","unused index","redundant index","sys","drop index","index cleanup"]},{"id":"buffer-pool-sizing","task":"Достаточен ли buffer pool: hit ratio, wait_free и изменение размера онлайн","category":"Память","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":"Показывает размер buffer pool, коэффициент попаданий в кэш, число свободных / грязных страниц и счётчик ожиданий свободной страницы (wait_free); меняет размер без перезапуска и отслеживает ход операции.","when":"При высоком IO на чтение, после добавления / удаления RAM или при планировании мощностей.","caution":"Сам по себе hit ratio обманчив: даже 99% может означать тысячи чтений с диска в секунду. Смотрите на прирост Innodb_buffer_pool_reads в секунду; wait_free > 0 означает, что buffer pool слишком мал или page cleaner не успевают. Новый размер округляется до кратного innodb_buffer_pool_chunk_size × instances. При изменении размера возможны кратковременные подвисания, особенно при уменьшении; избегайте часов пик. На выделенном сервере начинайте с 60–75% RAM; оставьте место под память на соединения, кэш ОС и другие процессы, так как своппинг всегда хуже, чем меньший buffer pool.","note":"Innodb_buffer_pool_resize_status_code / _progress появились в 8.0.31. innodb_dedicated_server=ON автоматически задаёт размер buffer pool и ёмкость redo по объёму RAM. sys.innodb_buffer_stats_by_table показывает использование buffer pool по таблицам; он сканирует INNODB_BUFFER_PAGE и тяжёл на больших buffer pool.","since":"MySQL 8.0","tags":["buffer pool","innodb","memory","hit ratio","resize","sizing"]},{"id":"partition-maintenance","task":"Обслуживание RANGE-секций: добавление будущих секций, архивирование / удаление старых","category":"Секционирование","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":"На таблице с RANGE-секционированием по дате добавляет секцию следующего периода, за миллисекунды переносит старый период в отдельную таблицу через EXCHANGE и удаляет секцию гораздо дешевле, чем DELETE.","when":"Для ежемесячного хранения и архивирования в растущих таблицах: журналы, события, аудит.","caution":"Столбец секционирования должен входить в каждый уникальный ключ (включая PK). REORGANIZE PARTITION pmax переписывает данные в pmax; держите pmax пустой и создавайте секции заранее (например, на 3 месяца). DROP PARTITION необратим и не журналируется построчно; на репликах выполняется как DDL. Целевая таблица EXCHANGE должна иметь ту же структуру и не быть секционированной; строки вне диапазона вызывают ошибку (WITHOUT VALIDATION быстрее, но может пропустить неверные данные). DDL по секциям берёт metadata lock на уровне таблицы. Для отсечения секций (pruning) используйте столбец секционирования в WHERE, не оборачивая его в функцию.","note":"В 8.0 секционирование поддерживают только InnoDB (и NDB). Добавление будущих / удаление старых секций обычно автоматизируют через Event Scheduler или cron; ALTER TABLE ... TRUNCATE PARTITION удаляет данные, но сохраняет секцию.","since":"MySQL 8.0","tags":["partition","range","reorganize","exchange partition","retention","archive"]},{"id":"roles-least-privilege","task":"Модель минимальных привилегий на ролях (CREATE ROLE, DEFAULT ROLE)","category":"Безопасность","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":"Выдаёт привилегии ролям, а не отдельным пользователям; учётные записи получают привилегии через членство в ролях. Изменения делаются в одном месте, аудит и отзыв доступа упрощаются.","when":"При создании новых учётных записей приложений / команд и при чистке старых серверов с запутанными привилегиями.","caution":"Роль, не являющаяся ролью по умолчанию, не активна в сессии (CURRENT_ROLE() NONE), и кажется, что у пользователя нет привилегий; используйте SET DEFAULT ROLE ALL или activate_all_roles_on_login=ON. Роль — это тоже учётная запись (заблокирована, без пароля) и видна в mysql.user. Для учётных записей приложений указывайте диапазон сети вместо '%'. Никогда не выдавайте учётной записи приложения ALL, SUPER, FILE, PROCESS или CONNECTION_ADMIN. Отзыв привилегии у роли затрагивает и активные сессии.","note":"mandatory_roles автоматически назначает роли всем учётным записям. CREATE USER ... DEFAULT ROLE и выдаёт роли, и делает их ролями по умолчанию.","since":"MySQL 8.0","tags":["role","grant","least privilege","default role","security","privilege"]},{"id":"password-policy-lockout","task":"Политика паролей, блокировка после неудачных входов и ротация пароля без простоя","category":"Безопасность","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":"Включает политику, отклоняющую слабые пароли, временно блокирует учётные записи после серии неудачных входов, задаёт срок жизни пароля и правила повторного использования; с двойными паролями пароль сервисной учётной записи меняется без простоя, пока приложения обновляются по одному.","when":"Для аудитов безопасности / соответствия требованиям (PCI, ISO 27001) и ротации паролей сервисных учётных записей.","caution":"Политика применяется только к новым / изменённым паролям; существующие слабые она не обнаруживает. На сервисных учётных записях FAILED_LOGIN_ATTEMPTS позволяет одному неверно настроенному клиенту заблокировать учётную запись и вызвать простой; используйте его для учётных записей людей. PASSWORD_LOCK_TIME задаётся в днях (UNBOUNDED: до ручной разблокировки); разблокировка — ALTER USER ... ACCOUNT UNLOCK. PASSWORD EXPIRE вызывает неожиданные простои на сервисных учётных записях; там предпочтительнее PASSWORD EXPIRE NEVER плюс ротация с двойным паролем.","note":"Двойные пароли появились в 8.0.14, FAILED_LOGIN_ATTEMPTS / PASSWORD_LOCK_TIME — в 8.0.19. Плагин validate_password из 5.7 в 8.0 заменён компонентом; имена переменных пишутся через точку (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":"Обязательный TLS, поиск соединений без TLS, обновление сертификатов без перезапуска","category":"Безопасность","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":"Показывает конфигурацию TLS сервера, срок действия сертификата и использует ли каждое соединение TLS; полностью запрещает незашифрованные TCP-соединения и обновляет сертификаты без остановки сервера.","when":"Для аудитов безопасности / соответствия требованиям, до истечения срока сертификата и при поиске старых клиентов, подключающихся без шифрования.","caution":"require_secure_transport=ON не влияет на соединения через Unix socket и shared memory; они считаются защищёнными. Перед включением найдите клиентов без TLS, иначе они получат ошибку 3159. ALTER INSTANCE RELOAD TLS не влияет на существующие соединения, только на новые; если файл сертификата некорректен, команда завершается ошибкой и сохраняет старый сертификат. Автоматически сгенерированный самоподписанный сертификат клиенты не могут проверить с VERIFY_CA / VERIFY_IDENTITY; на продакшене используйте сертификат, подписанный вашим CA.","note":"ALTER INSTANCE RELOAD TLS появился в 8.0.16, performance_schema.tls_channel_status — в 8.0.21. Для требования на уровне учётной записи используйте ALTER USER ... REQUIRE SSL (или REQUIRE X509).","since":"MySQL 8.0.21","tags":["tls","ssl","require_secure_transport","certificate","encryption","cert"]},{"id":"risky-accounts-audit","task":"Аудит рискованных учётных записей: пустой пароль, хост '%', SUPER, mysql_native_password","category":"Безопасность","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":"Выводит в одной строке находки по безопасности для каждой учётной записи: нет пароля, подключение откуда угодно, устаревший плагин аутентификации, привилегии SUPER / GRANT / FILE и отсутствие требования TLS; второй запрос показывает опасные динамические привилегии.","when":"При периодических аудитах безопасности, на только что унаследованном сервере и перед обновлением до 8.4.","caution":"Запрос читает mysql.user напрямую и требует SELECT на ней. Учётные записи ролей заблокированы и без пароля, поэтому EMPTY PASSWORD сообщается только для незаблокированных учётных записей. Super_priv — статическая привилегия SUPER; так как 8.0 разбивает её на динамические привилегии, проверьте и второй запрос. Учётные записи с пометкой NATIVE AUTH нужно перевести на caching_sha2_password до 8.4: ALTER USER 'u'@'h' IDENTIFIED WITH caching_sha2_password BY '...'; старые клиенты / коннекторы могут не поддерживать caching_sha2_password, сначала протестируйте их.","note":"mysql_native_password объявлен устаревшим в 8.0.34, в 8.4 отключён по умолчанию (можно включить через mysql_native_password=ON) и удалён в 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 (util.checkForServerUpgrade)","category":"Обновление","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":"Проверяет текущий сервер относительно целевой версии: удалённые переменные и привилегии, параметры с новыми значениями по умолчанию, устаревшие методы аутентификации, некорректные определения столбцов, проблемы с внешними ключами и секциями. Находки уровня Error блокируют обновление.","when":"Перед каждым мажорным / LTS-обновлением; повторите после исправления находок.","caution":"Просмотрите и предупреждения: в 8.4 меняются значения по умолчанию, такие как innodb_io_capacity (10000), innodb_log_buffer_size (64 MB), innodb_adaptive_hash_index (OFF), innodb_change_buffering (none), innodb_buffer_pool_instances и group_replication_consistency, что может повлиять на производительность; зафиксируйте в my.cnf значения, текущее поведение которых хотите сохранить. Удалённые привилегии, такие как SET_USER_ID, при обновлении удаляются. Сначала обновите реплику, понаблюдайте за ней под нагрузкой, затем переключитесь; бинарный откат с 8.4 на 8.0 не поддерживается, поэтому путь отката — логический бэкап или реплика, оставленная на старой версии.","note":"Удалено в 8.4: старый синтаксис репликации, такой как START / STOP SLAVE, SHOW SLAVE STATUS и CHANGE MASTER TO, default_authentication_plugin (заменён authentication_policy), binlog_transaction_dependency_tracking и mysqlpump. mysql_native_password отключён по умолчанию.","since":"MySQL Shell 8.0","tags":["upgrade","8.4","checkforserverupgrade","mysqlsh","lts","version upgrade"]},{"id":"charset-utf8mb4-convert","task":"Конвертация таблиц latin1 / utf8mb3 в utf8mb4","category":"Обслуживание","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":"Находит столбцы и таблицы не в utf8mb4 и генерирует DDL для конвертации; CONVERT TO меняет и кодировку таблицы по умолчанию, и все символьные столбцы, позволяя хранить эмодзи / 4-байтовые символы.","when":"На схемах, перенесённых с 5.x, при ошибках 'Incorrect string value' (1366) и когда соединения между столбцами с разной сортировкой (collation) не могут использовать индексы.","caution":"CONVERT TO переписывает таблицу через COPY и блокирует запись на всё время операции (INPLACE не поддерживается); на больших таблицах выполняйте тот же --alter через pt-online-schema-change / gh-ost. utf8mb4 использует 4 байта на символ: индексированный VARCHAR(255) становится 1020 байт; лимит — 3072 байта для формата строк DYNAMIC / COMPRESSED и 767 для COMPACT / REDUNDANT, сверх которого возникает ошибка 1071. Столбцы VARCHAR / TEXT при необходимости повышаются до большего типа (TEXT → MEDIUMTEXT), чтобы сохранить ту же символьную ёмкость. Смена collation влияет на сортировку и сравнение: utf8mb4_0900_ai_ci нечувствительна к диакритике и регистру, поэтому ранее различавшиеся значения могут столкнуться в уникальном индексе (1062). Конвертируйте соединяемые таблицы вместе. Клиентское соединение тоже должно использовать utf8mb4.","note":"utf8 (utf8mb3) в 8.0 объявлен устаревшим; начиная с 8.0.30 в SHOW CREATE TABLE отображается как utf8mb3. Для турецких правил сортировки (I с точкой и без точки) используйте utf8mb4_tr_0900_ai_ci. По умолчанию в 8.0 — 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":"Резервная копия журнала транзакций","category":"Бэкап и журнал","command":"BACKUP LOG [Customer]\nTO DISK = N'D:\\Backup\\Customer_log_20260706_1200.trn'\nWITH COMPRESSION, CHECKSUM, STATS = 10;","what":"Создаёт резервную копию журнала транзакций и (в модели восстановления FULL/BULK_LOGGED) усекает использованную часть журнала, чтобы её можно было переиспользовать.","when":"Регулярно в модели восстановления FULL (например, каждые 15 мин) для цепочки восстановления на момент времени и при росте файла журнала.","caution":"Имеет смысл только в модели восстановления FULL/BULK_LOGGED (в SIMPLE журнал усекается автоматически). Сначала должна существовать хотя бы одна FULL-копия, чтобы не разорвать цепочку журналов. Настройте правильную стратегию удаления/перезаписи файлов резервных копий.","tags":["backup log","log backup","transaction log","trn","recovery","backup"]},{"id":"backup-full-diff","task":"Резервные копии FULL и DIFFERENTIAL","category":"Бэкап и журнал","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 копирует всю базу данных; DIFFERENTIAL — экстенты, изменённые после последней FULL.","when":"FULL: ежедневная/еженедельная базовая копия. DIFF: быстрые промежуточные копии между FULL.","caution":"Для восстановления DIFF нужна её базовая FULL-копия. INIT перезаписывает существующий файл; при неверном имени файла вы теряете резервную копию. CHECKSUM рано выявляет повреждения.","tags":["backup","full","differential","differential backup","bak","backup"]},{"id":"log-reuse-wait","task":"Почему вырос файл журнала / почему он не усекается","category":"Бэкап и журнал","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":"Столбец log_reuse_wait_desc показывает, почему журнал транзакций не может быть усечён (например, LOG_BACKUP, ACTIVE_TRANSACTION, REPLICATION, AVAILABILITY_REPLICA).","when":"Для поиска первопричины, когда файл журнала продолжает расти или журнал заполнен (ошибка 9002).","caution":"Если показан LOG_BACKUP, решение — сделать резервную копию журнала, а не сжимать файл. Только запрос; без риска.","tags":["log_reuse_wait_desc","log growth","sys.databases","log backup","recovery","diagnostics"]},{"id":"shrink-log","task":"Сжатие файла журнала","category":"Бэкап и журнал","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":"Уменьшает физический размер файла журнала транзакций и возвращает место операционной системе.","when":"Когда журнал остался постоянно раздутым после разового аномального роста (большая транзакция/перестроение индекса).","caution":"ОПАСНО/ОСТОРОЖНО: регулярное сжатие — ПЛОХАЯ практика; производительность падает, пока журнал снова растёт, и усиливается фрагментация VLF. Сначала сделайте резервную копию журнала, иначе размер не уменьшится. Сжимайте только излишне выросший файл журнала, а не файл данных. Затем увеличьте его до подходящего размера за один шаг.","tags":["shrink","dbcc shrinkfile","shrink log","log","vlf","caution"]},{"id":"vlf-count","task":"Количество VLF (Virtual Log File)","category":"Бэкап и журнал","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":"Возвращает число виртуальных файлов журнала (VLF) внутри журнала транзакций. Слишком большое число VLF ухудшает производительность запуска/восстановления/резервного копирования журнала.","when":"Когда журнал работает медленно или база долго запускается; при пересмотре настройки роста журнала.","caution":"Сотни/тысячи VLF — тревожный признак; решение — сжать журнал и снова увеличить его достаточно крупными шагами (например, по 8GB). Только запрос; без риска.","since":"SQL Server 2016 SP2","note":"DMV sys.dm_db_log_info появилось в SQL Server 2016 SP2 и 2017; до этого VLF считали по числу строк вывода недокументированной DBCC LOGINFO.","tags":["vlf","dm_db_log_info","dbcc loginfo","log","virtual log file","performance"]},{"id":"open-transaction","task":"Поиск самой старой открытой транзакции (ACTIVE_TRANSACTION)","category":"Бэкап и журнал","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":"Показывает самую старую открытую (не зафиксированную/не откатанную) транзакцию и удерживающую её сессию. Открытая транзакция мешает усечению журнала (LOG_REUSE_WAIT = ACTIVE_TRANSACTION).","when":"Для поиска первопричины, когда журнал не уменьшается несмотря на резервные копии журнала и log_reuse_wait_desc = ACTIVE_TRANSACTION.","caution":"Перед KILL сессии с долгой открытой транзакцией согласуйте это с владельцем бизнес-процесса; откат может занять много времени. Только запрос; без риска.","tags":["dbcc opentran","open transaction","active_transaction","dm_tran_active_transactions","log","diagnostics"]},{"id":"add-data-file","task":"Добавление файла данных в базу данных","category":"Хранение","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":"Добавляет новый файл данных (.ndf) в существующую файловую группу для увеличения ёмкости и распределения I/O.","when":"Когда диск заполнен или нужно распределить I/O по нескольким дискам.","caution":"Запись в новый файл балансируется пропорциональным заполнением; первым заполняется самый пустой файл. Указывайте FILEGROWTH фиксированным значением в MB, а не в процентах. MAXSIZE предотвращает неконтролируемый рост.","tags":["add file","add data file","ndf","alter database","filegroup","storage"]},{"id":"add-filegroup","task":"Добавление файловой группы","category":"Хранение","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":"Создаёт новую файловую группу с файлом; позволяет размещать таблицы/индексы на отдельном диске/файловой группе.","when":"Для отделения архивных/холодных данных, резервного копирования/восстановления на уровне файловых групп и разделения I/O.","caution":"Пустую файловую группу нельзя использовать/скопировать; добавьте в неё хотя бы один файл. Смена файловой группы по умолчанию влияет на размещение новых объектов.","tags":["filegroup","filegroup","add filegroup","alter database","ndf","storage"]},{"id":"modify-file-growth","task":"Изменение размера файла / автоувеличения","category":"Хранение","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":"Заранее увеличивает файл данных/журнала или задаёт шаг/верхний предел автоувеличения.","when":"Чтобы исправить неконтролируемые мелкие шаги роста, заранее выделить место или задать MAXSIZE.","caution":"Процентное автоувеличение даёт очень крупные шаги на больших файлах; используйте фиксированные MB. Без Instant File Initialization рост файла данных блокирует работу. SIZE не может уменьшить файл (нужно сжатие).","tags":["modify file","autogrowth","filegrowth","size","alter database","storage"]},{"id":"move-file","task":"Перенос файла данных/журнала в другое место","category":"Хранение","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":"Меняет физическое расположение файла базы данных: новый путь задаётся в метаданных, файл физически переносится, пока база в OFFLINE, затем база переводится в ONLINE.","when":"При замене диска или переносе на другой диск ради свободного места.","caution":"OFFLINE + ROLLBACK IMMEDIATE разрывает все активные соединения; планируйте простой. Не переводите базу в ONLINE, пока файл действительно не скопирован по новому пути в ОС, иначе база станет SUSPECT. Для tempdb нужен другой способ (MODIFY FILE + перезапуск службы SQL Server).","tags":["move file","move file","modify file","offline","online","mdf","ldf","storage"]},{"id":"add-tempdb-file","task":"Добавление файла данных в tempdb","category":"Хранение","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":"Добавляет в tempdb дополнительный файл данных для снижения конкуренции за выделение (PFS/GAM/SGAM) и распределения I/O.","when":"В системах с конкуренцией в tempdb (ожидания PAGELATCH_UP) или интенсивным использованием tempdb.","caution":"Все файлы данных tempdb должны иметь ОДИНАКОВЫЙ размер и шаг роста (для пропорционального заполнения). Добавленный файл используется сразу без перезапуска; если существующие файлы разного размера, сначала выровняйте их все, иначе новый (пустой) файл получит непропорциональную нагрузку. Число файлов обычно соответствует числу ядер (до 8); установщик SQL Server 2016+ делает так по умолчанию.","tags":["tempdb","add file","tempdb file","ndf","contention","pagelatch","storage"]},{"id":"index-rebuild-reorg","task":"Перестроение / реорганизация индекса","category":"Обслуживание","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 создаёт индекс заново (полностью устраняет фрагментацию, обновляет статистику); REORGANIZE уплотняет конечный уровень на месте (легковесно, без блокировок).","when":"REORGANIZE при фрагментации 5-30%, REBUILD — выше 30%. Для больших таблиц — в окна обслуживания.","caution":"ONLINE = ON доступен только в Enterprise/Developer; в Standard используйте OFF (таблица блокируется). REBUILD генерирует много журнала и требует места. MAXDOP и SORT_IN_TEMPDB влияют на использование ресурсов.","tags":["index","rebuild","reorganize","alter index","fragmentation","maintenance","online"]},{"id":"update-statistics","task":"Обновление статистики","category":"Обслуживание","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":"Обновляет статистику столбцов/индексов, которую использует оптимизатор; обеспечивает правильные планы выполнения и оценки кардинальности.","when":"После крупных изменений/загрузок данных, при регрессии планов; перестроение индекса и так полностью обновляет статистику (отдельно не нужно).","caution":"FULLSCAN точен, но дорог; создаёт нагрузку I/O на больших таблицах. sp_updatestats обновляет только изменившуюся статистику и использует сэмплирование по умолчанию. Повторный UPDATE STATISTICS по тому же индексу после REBUILD не нужен.","tags":["update statistics","statistics","sp_updatestats","fullscan","optimizer","maintenance"]},{"id":"dbcc-checkdb","task":"Проверка целостности (DBCC CHECKDB)","category":"Обслуживание","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":"Проверяет физическую и логическую целостность базы данных (структуры страниц, выделение, согласованность индексов) и сообщает о повреждениях.","when":"При регулярном обслуживании (еженедельно), при подозрении на проблемы оборудования/IO, после ошибок 823/824.","caution":"Интенсивно использует I/O и tempdb; запускайте в окно обслуживания. При обнаружении ошибок REPAIR_ALLOW_DATA_LOSS должен быть ПОСЛЕДНИМ средством и ПРИВОДИТ К ПОТЕРЕ ДАННЫХ; сначала рассмотрите восстановление из исправной резервной копии. PHYSICAL_ONLY быстрее, но пропускает логические проверки.","tags":["dbcc checkdb","integrity","corruption","corruption","integrity","maintenance"]},{"id":"index-fragmentation","task":"Фрагментация индексов","category":"Обслуживание","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":"Выводит процент фрагментации и число страниц индексов; основа для решения о rebuild/reorganize.","when":"Перед обслуживанием, чтобы решить, какими индексами заняться.","caution":"На маленьких индексах (< 1000 страниц) фрагментация не важна; они отфильтрованы. Режим 'DETAILED' точнее, но очень дорог; обычно достаточно 'LIMITED'. Только запрос; без риска.","tags":["fragmentation","dm_db_index_physical_stats","index","frag","maintenance","diagnostics"]},{"id":"kill-spid","task":"Завершение сессии (SPID)","category":"Сессия","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":"Завершает указанный session_id (SPID); его открытая транзакция откатывается.","when":"Чтобы остановить сессию, которая удерживает блокировки, выполняет вышедший из-под контроля/долгий запрос или не освобождает соединение.","caution":"При большой открытой транзакции откат может длиться долго и не может быть отменён; следите за ходом через KILL WITH STATUSONLY. Не завершайте системные SPID (is_user_process=0). Убедитесь, что SPID верный.","tags":["kill","spid","kill session","dm_exec_sessions","dm_exec_requests","session"]},{"id":"blocking-chain","task":"Поиск цепочки блокировок","category":"Сессия","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":"Выводит ожидающие сессии и блокирующие их сессии со временем ожидания и выполняемым запросом; делает цепочку блокировок наглядной.","when":"Для поиска источника конкуренции за блокировки при жалобах \"приложение зависает / таймауты\".","caution":"Перед завершением корневого блокировщика (SPID, которого никто не блокирует, но который блокирует других) согласуйте это с владельцем бизнес-процесса. Только запрос; без риска.","tags":["blocking","lock","lock","dm_exec_requests","blocking_session_id","session","chain"]},{"id":"active-queries","task":"Активные запросы и их стоимость","category":"Сессия","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":"Показывает выполняющиеся пользовательские запросы с CPU, затраченным временем, чтениями/записями и точным текстом команды.","when":"Чтобы найти, какой запрос потребляет ресурсы во время замедления в реальном времени.","caution":"statement_start/end_offset извлекает точную выполняемую команду (а не весь пакет). Только запрос; без риска.","tags":["active queries","active query","dm_exec_requests","dm_exec_sql_text","cpu","session"]},{"id":"sp-configure-memory","task":"Настройка через sp_configure (max server memory)","category":"Конфигурация","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":"Ограничивает максимальный объём памяти, который SQL Server может использовать под буферный пул; оставляет RAM операционной системе и другим компонентам.","when":"Когда ОС/другим службам на сервере не хватает памяти или добавлена новая RAM.","caution":"Оставьте достаточно RAM для операционной системы, SSAS/SSIS, CLR и стеков потоков (обычно не менее 4-6 GB для ОС). Слишком низкое значение калечит производительность SQL Server. Без RECONFIGURE изменение не применяется; эта настройка динамическая (перезапуск не нужен).","tags":["sp_configure","max server memory","reconfigure","memory","configuration","memory"]},{"id":"reconfigure-general","task":"Общая настройка sp_configure и RECONFIGURE","category":"Конфигурация","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":"Задаёт параметры конфигурации уровня сервера; RECONFIGURE применяет их к рабочему значению.","when":"При изменении серверных настроек, таких как MAXDOP, порог параллелизма или optimize for ad hoc workloads.","caution":"Настройки с is_dynamic = 0 требуют перезапуска SQL Server. RECONFIGURE WITH OVERRIDE для некоторых настроек пропускает проверку; используйте осторожно. Перед любым изменением запишите value_in_use.","tags":["sp_configure","reconfigure","maxdop","cost threshold","sys.configurations","configuration"]},{"id":"ag-failover","task":"Отработка отказа Availability Group","category":"Конфигурация","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":"Переносит роль первичной реплики Availability Group на другую реплику; плановая отработка отказа на синхронную реплику проходит без потери данных.","when":"Передача роли для обслуживания/установки патчей, отработка отказа при сбое первичного узла.","caution":"Обычная команда FAILOVER выполняется на ВТОРИЧНОЙ реплике, которая примет роль. FORCE_FAILOVER_ALLOW_DATA_LOSS используется только при недоступности первичной реплики или на асинхронной реплике и может ПРИВЕСТИ К ПОТЕРЕ ДАННЫХ; будьте очень осторожны, если синхронизация не в норме.","tags":["availability group","ag","failover","hadr","failover","alwayson","configuration"]},{"id":"change-recovery-model","task":"Изменение модели восстановления базы данных","category":"Конфигурация","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":"Меняет модель восстановления базы данных; определяет, как управляется журнал и какие варианты восстановления доступны.","when":"FULL: когда нужны восстановление на момент времени/резервные копии журнала/AG. SIMPLE: в системах, которым не нужна точка восстановления (dev/test, DW).","caution":"После перехода на FULL сделайте FULL-копию, чтобы начать цепочку журналов, и настройте регулярные резервные копии журнала, иначе журнал будет расти. Переход с FULL на SIMPLE разрывает цепочку журналов; вы теряете восстановление на момент времени. Участники AG должны быть в модели восстановления FULL.","tags":["recovery model","recovery full","simple","bulk_logged","alter database","configuration"]}]},"recipes/mssql-ext.json":{"engineId":"mssql","engine":"SQL Server","order":2,"recipes":[{"id":"ag-health-lag","task":"Мониторинг состояния и отставания группы доступности (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":"Показывает состояние подключения и синхронизации реплик; для каждой базы — очередь отправки журнала, очередь redo, скорости отправки/redo, оценку времени догона redo и разрыв последней фиксации относительно первичной реплики (окно потенциальной потери данных); также возвращает состояние кворума WSFC.","when":"При оповещении NOT_HEALTHY / NOT SYNCHRONIZING на панели AG, при проверке RPO/RTO перед плановой отработкой отказа или когда пользователи видят устаревшие данные на читаемой вторичной реплике.","caution":"Растущая log_send_queue — это объём журнала, теряемый при сбое первичной реплики (RPO); растущая redo_queue — сколько времени база будет переходить в онлайн после отработки отказа (RTO). На вторичной реплике видна только локальная реплика; для полной картины запускайте на первичной. data_loss_window_s имеет смысл только при наличии записи. Запрос только на чтение; безопасно.","since":"2016","note":"secondary_lag_seconds — столбец 2016+. Если очередь redo продолжает расти, проверьте, не заблокирован ли поток redo на вторичной реплике (долгие запросы на чтение, DDL), и ищите ожидания REDO_THREAD_PENDING_WORK / PARALLEL_REDO_*.","tags":["availability group","ag","log send queue","redo queue","synchronization","rpo"],"untested":true},{"id":"ag-forced-failover-dr","task":"Принудительная отработка отказа (FORCE_FAILOVER_ALLOW_DATA_LOSS) и восстановление после неё","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":"При полной потере первичной реплики делает DR-реплику первичной с принятием потери данных; затем возобновляет приостановленное перемещение данных на остальных репликах и, когда старая первичная возвращается, делает снимок для изучения транзакций, не дошедших до новой первичной.","when":"Когда основной ЦОД недоступен, осталась только асинхронная реплика или синхронная реплика не SYNCHRONIZED, а отработка отказа всё равно нужна.","caution":"ПОТЕРЯ ДАННЫХ: транзакции после последнего hardened LSN теряются. Сначала получите согласие бизнеса по RPO. После принудительной отработки отказа все вторичные базы находятся в SUSPENDED; журнал не усекается и растёт до HADR RESUME. Если кворум WSFC потерян, сначала запустите кластер с принудительным кворумом (PowerShell: Start-ClusterNode -FixQuorum), иначе команда завершится ошибкой. HADR RESUME на старой первичной реплике окончательно отбрасывает потерянные транзакции; не возобновляйте без снимка.","note":"Для плановой отработки отказа без потери данных используйте команду FAILOVER из рецепта ag-failover. AG с CLUSTER_TYPE = NONE / EXTERNAL также используют FORCE_FAILOVER_ALLOW_DATA_LOSS; при EXTERNAL (Pacemaker) отработкой отказа управляет диспетчер кластера. Для снимка нужен каждый логический файл данных исходной базы; для многофайловых баз добавьте по одной записи ON на каждый файл данных.","tags":["force failover","allow data loss","dr","availability group","hadr resume","data loss"],"untested":true},{"id":"ag-add-db-autoseed","task":"Добавление базы в AG (автоматическое заполнение) и мониторинг хода заполнения","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":"Добавляет базу в AG, передавая её на вторичные реплики через конечную точку (VDI) без ручного backup/restore; показывает пропускную способность заполнения, оценку завершения и причину сбоя.","when":"При добавлении новой базы в AG, добавлении новой реплики или пересоздании сломанной вторичной базы.","caution":"Заполнение нагружает канал конечной точки до предела и по умолчанию идёт без сжатия; для очень больших баз или слабого WAN ручной backup/restore + JOIN предсказуемее. Журнал первичной реплики во время заполнения не усекается (log_reuse_wait_desc = AVAILABILITY_REPLICA). В 2016 вторичной реплике нужны те же пути папок данных/журнала, что и у первичной; в 2017+, если файлы первичной реплики лежат в путях экземпляра по умолчанию, вторичная использует собственные пути по умолчанию. Для базы с TDE сертификат сначала нужно восстановить на каждой вторичной реплике. Без GRANT CREATE ANY DATABASE на вторичной реплике заполнение остаётся в ожидании.","since":"2016","note":"Сжатие при заполнении включается флагом трассировки 9567 (расходует CPU). Начало, завершение и сбои заполнения пишутся в журнал ошибок SQL Server; для подробного отслеживания создайте отдельный сеанс Extended Events с событиями hadr_automatic_seeding_* и hadr_physical_seeding_*. Возврат — 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":"Проверка прослушивателя, его IP и маршрутизации только для чтения","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":"Показывает DNS-имя прослушивателя, порт и состояние в кластере каждого IP подсети; для каждого сценария первичной реплики в списке маршрутизации только для чтения — на какую реплику и URL направляются подключения с намерением чтения.","when":"Когда приложения не могут подключиться после отработки отказа, подключения ApplicationIntent=ReadOnly попадают на первичную реплику или добавлена новая подсеть/реплика.","caution":"Маршрутизация только для чтения работает лишь для подключений через прослушиватель с ApplicationIntent=ReadOnly и явно указанной базой; прямые подключения к имени экземпляра не маршрутизируются. Реплика с пустым read_only_routing_url не может быть целью маршрутизации. На прослушивателе с несколькими подсетями клиенты без MultiSubnetFailover=True получают таймауты подключения 20-30 с после отработки отказа. Запрос только на чтение; безопасно.","note":"Для балансировки нагрузки в 2016+ список маршрутизации использует вложенные скобки: READ_ONLY_ROUTING_LIST = (('SQL02','SQL03'),'SQL01'). Если IP прослушивателя в состоянии OFFLINE, проверьте зависимости ресурсов на стороне кластера (Failover Cluster Manager / Get-ClusterResource).","tags":["listener","read-only routing","applicationintent","multisubnetfailover","availability group","connection"],"untested":true},{"id":"logshipping-monitor","task":"Мониторинг состояния и задержки доставки журналов (log shipping)","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":"Показывает возраст последней резервной копии журнала на первичном сервере, последний скопированный/восстановленный файл и задержку восстановления на вторичном вместе с их порогами, а также ошибки заданий backup/copy/restore за последние 24 часа.","when":"Когда срабатывает задание оповещения log shipping (ошибки 14420/14421), перед DR-тестом или для отчёта о том, насколько отстаёт вторичный сервер.","caution":"Таблицы мониторинга содержат только ту сторону, которую видит сервер: таблица primary заполняется на первичном сервере/мониторе, таблица secondary — на вторичном/мониторе. Ручная (вне log shipping) резервная копия журнала разрывает цепочку, и задание restore продолжает падать; для разовых резервных копий журнала используйте COPY_ONLY. В режиме STANDBY пользовательские подключения разрываются перед каждым восстановлением. Запрос только на чтение; безопасно.","note":"Столбцы дат — в локальном времени сервера; при сравнении серверов в разных часовых поясах используйте столбцы *_utc. Если задание restore пишет \"skipping log backup file\", этот файл уже применён; чтобы найти настоящий разрыв, проверьте последовательность FirstLSN/LastLSN через RESTORE HEADERONLY на вторичном сервере.","tags":["log shipping","sp_help_log_shipping_monitor","restore latency","dr","secondary","latency"],"untested":true},{"id":"backup-copy-only","task":"Разовая резервная копия без разрыва цепочки (COPY_ONLY, striping, пропускная способность)","category":"Бэкап и журнал","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":"Создаёт самостоятельную FULL-копию или копию журнала, не влияющую на регулярную цепочку (база для разностных копий и цепочка журналов); для больших баз распределение по нескольким файлам (striping) и больший размер передачи сокращают время копирования.","when":"При передаче копии в test/dev, перед рискованным изменением или по запросу резервной копии для аудита; всегда, когда нужна копия без вмешательства в плановую стратегию резервного копирования.","caution":"Разовая FULL без COPY_ONLY становится базой последующих DIFF-копий; если этот файл удалён, DIFF бесполезны. COPY_ONLY вместе с DIFFERENTIAL не действует. Для восстановления striped-копии нужны все файлы; потеря одного делает всю копию непригодной. Память BUFFERCOUNT x MAXTRANSFERSIZE выделяется вне буферного пула (256 MB в примере); на сервере с нехваткой памяти уменьшите её.","note":"Для базы с TDE сжатие резервной копии требует MAXTRANSFERSIZE больше 65536 (2016+). Серверные значения по умолчанию: sp_configure 'backup compression default' и 'backup checksum default'. В 2022 с оборудованием Intel QAT доступно WITH COMPRESSION (ALGORITHM = QAT_DEFLATE).","tags":["copy_only","ad-hoc backup","stripe","maxtransfersize","buffercount","backup"]},{"id":"backup-to-url","task":"Резервное копирование по URL (Azure Blob SAS / S3-совместимое хранилище)","category":"Бэкап и журнал","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":"Записывает резервную копию прямо в объектное хранилище: в Azure Blob в виде блочных BLOB-объектов с учётными данными SAS или, в 2022, в S3 API-совместимое хранилище (AWS S3, MinIO и т. п.) с учётными данными ключа доступа. Учётные данные выбираются автоматически по совпадению префикса URL.","when":"Для требований внеплощадочного хранения копий, сценариев миграции в облако/гибридного DR или когда локального хранилища для резервных копий недостаточно.","caution":"В Azure один блочный BLOB-объект вмещает не более 50,000 блоков; при MAXTRANSFERSIZE 4 MB это примерно 195 GB на файл, поэтому большие базы распределяйте по нескольким URL. В S3 у файла не более 10,000 частей; для больших баз увеличьте MAXTRANSFERSIZE (до 20 MB, требуется COMPRESSION) и распределите по нескольким URL (до 64). S3 не поддерживает INIT/дозапись; FORMAT перезаписывает существующую копию с тем же именем. Когда SAS истекает, задания резервного копирования падают с ошибкой; занесите дату окончания в календарь. Секрет учётных данных хранится в master; его могут использовать участники с CONTROL SERVER.","since":"2022","note":"Резервное копирование в блочный BLOB с SAS — 2016+, поддержка S3 — 2022+. Устаревший способ с ключом учётной записи хранения (IDENTITY = имя учётной записи) создаёт страничные BLOB-объекты и не рекомендуется. Для S3 сервер должен доверять TLS-сертификату конечной точки; для локальных решений, таких как MinIO, порт указывается в URL (s3://minio.local:9000/bucket/...).","tags":["backup to url","azure blob","s3","credential","sas","cloud backup"],"untested":true},{"id":"backup-history-msdb","task":"История резервного копирования из msdb и проверка отсутствующих копий","category":"Бэкап и журнал","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":"Выводит для каждой базы время последней FULL/DIFF/LOG-копии и возраст копии журнала; для одной базы — цепочку копий с последовательностью LSN, сжатым размером, флагом контрольной суммы и путём к файлу.","when":"При утренней проверке, чтобы найти файлы, нужные для восстановления, ответить на аудите \"когда была последняя копия\" или проверить, идут ли копии журнала при оповещении о заполнении журнала.","caution":"msdb знает только о копиях, сделанных этим экземпляром; копии с другого сервера или другой реплики AG не видны (в AG проверяйте каждую реплику). first_lsn копии журнала должен совпадать с last_lsn предыдущей; разрыв означает, что цепочка нарушена. В модели FULL значение NULL в last_log означает, что журнал никогда не копируется и будет расти. Запрос только на чтение; безопасно.","note":"История msdb растёт без ограничений и замедляет задания резервного копирования; регулярно очищайте её через EXEC msdb.dbo.sp_delete_backuphistory @oldest_date = '2026-01-01';. Значения type: D = FULL, I = DIFF, L = LOG, F = файл/файловая группа.","tags":["backupset","msdb","backup history","last backup","lsn","backup chain"]},{"id":"backup-restore-progress","task":"Ход backup / restore / CHECKDB и оценка завершения","category":"Бэкап и журнал","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":"Показывает процент выполнения, ожидаемое время окончания и текущее ожидание для долгих операций, сообщающих percent_complete (BACKUP, RESTORE, DBCC CHECKDB/SHRINK, ALTER INDEX REORGANIZE, откат после KILL).","when":"Когда ночная резервная копия заходит на рабочие часы, чтобы ответить \"когда закончится\" при большом восстановлении или отследить откат завершённой сессии.","caution":"Оценка предполагает линейный ход; в фазах redo/undo восстановления и при обнулении журнала процент может долго стоять на месте. Файлы журнала не используют мгновенную инициализацию файлов (кроме приращений до 64 MB в 2022); восстановления с большими журналами долго находятся в первой фазе. Запрос только на чтение; безопасно.","note":"Если wait_type в основном BACKUPIO / ASYNC_IO_COMPLETION, узкое место — целевой/исходный диск; высокие BACKUPTHREAD или CPU указывают на стоимость сжатия. Чтобы подробно видеть долгую фазу обнуления восстановления в журнале ошибок, используйте флаги трассировки 3004 + 3605.","tags":["percent_complete","backup progress","restore progress","eta","dm_exec_requests","progress"]},{"id":"tail-log-backup","task":"Резервная копия заключительного фрагмента журнала перед восстановлением (NORECOVERY / NO_TRUNCATE)","category":"Восстановление","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":"Копирует часть журнала, записанную после последней резервной копии журнала (заключительный фрагмент), чтобы цепочка восстановления дошла до момента сбоя и последние минуты транзакций не были потеряны.","when":"Перед восстановлением поверх той же базы, при отказе диска с файлами данных или при финальном переключении миграции.","caution":"NORECOVERY переводит базу в RESTORING и прекращает доступ пользователей; не выполните это на не той базе. Восстановление WITH REPLACE без копии заключительного фрагмента журнала окончательно отбрасывает транзакции после последней копии журнала. Копия, сделанная с NO_TRUNCATE, может содержать неполные метаданные; после восстановления выполните DBCC CHECKDB. В модели SIMPLE копия заключительного фрагмента журнала невозможна.","note":"Чтобы вывести базу из RESTORING без восстановления: RESTORE DATABASE [Musteri] WITH RECOVERY;. NO_TRUNCATE эквивалентен COPY_ONLY + CONTINUE_AFTER_ERROR и пытается прочитать журнал, даже если базу нельзя открыть.","tags":["tail-log","backup log","norecovery","no_truncate","restore","recovery"]},{"id":"restore-pitr-stopat","task":"Восстановление на момент времени (point-in-time restore, STOPAT)","category":"Восстановление","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":"Применяет FULL + (необязательно) DIFF + копии журнала WITH NORECOVERY и останавливает воспроизведение журнала в заданное время; возвращает базу в состояние непосредственно перед ошибочной операцией (неверный DELETE/UPDATE, DROP).","when":"Для восстановления случайно удалённых/изменённых данных, изучения данных на определённый момент или для аудита.","caution":"Восстанавливайте под другим именем, а не поверх оригинала, затем копируйте нужные строки обратно; если восстанавливать поверх необходимо, сначала сделайте копию заключительного фрагмента журнала. STOPAT — в локальном времени сервера. Модель восстановления должна быть FULL; если копия журнала, содержащая время STOPAT, включает операции с минимальным протоколированием под BULK_LOGGED, остановиться внутри этой копии нельзя. После RESTORE ... WITH RECOVERY больше журналов применить нельзя.","note":"Рекомендуемый шаблон — указывать STOPAT при каждом восстановлении журнала; информационное сообщение \"contains records that were logged before the designated point in time\" на более ранних файлах нормально. Для остановки на именованной метке (BEGIN TRAN ... WITH MARK) используйте STOPATMARK / STOPBEFOREMARK. Для поиска точного времени используйте fn_dump_dblog или журналы приложения.","tags":["stopat","point-in-time","restore log","norecovery","standby","rollback to time"]},{"id":"restore-verify-header","task":"Изучение и проверка файла резервной копии (VERIFYONLY, HEADERONLY, FILELISTONLY)","category":"Восстановление","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":"Проверяет, что копия читается и контрольные суммы страниц корректны, не восстанавливая её; показывает наборы копий внутри (Position, BackupType, FirstLSN/LastLSN, DatabaseVersion), логические имена файлов и сведения о stripe.","when":"Перед восстановлением копии, полученной от другой команды, чтобы узнать имена файлов для MOVE, как быструю проверку после задания резервного копирования или чтобы выбрать правильный FILE = n, когда файл содержит несколько наборов.","caution":"VERIFYONLY не гарантирует, что базу можно восстановить и она согласована; проверяется только структура копии и (если она сделана WITH CHECKSUM) контрольные суммы страниц. Для настоящей уверенности регулярно выполняйте тестовые восстановления + DBCC CHECKDB. Копию из более новой версии нельзя восстановить на более старой (проверьте DatabaseVersion). Операции чтения; безопасно.","note":"Если HEADERONLY показывает HasBackupChecksums = 0, копия сделана без CHECKSUM, и VERIFYONLY WITH CHECKSUM не может проверить страницы. Файлы, записанные без INIT, накапливают несколько наборов; при восстановлении укажите WITH FILE = <Position>.","tags":["restore verifyonly","headeronly","filelistonly","labelonly","backup verification","checksum"]},{"id":"page-restore-suspect","task":"Поиск повреждённых страниц (suspect_pages) и восстановление отдельных страниц","category":"Восстановление","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":"Выводит из msdb.dbo.suspect_pages страницы с ошибками I/O (823/824), контрольной суммы или torn page; исправляет отдельные повреждённые страницы, беря их из FULL-копии и накатывая журналы, без восстановления всей базы.","when":"При ошибках 823/824 в журнале ошибок, ошибках согласованности в CHECKDB, ограниченных несколькими страницами, или когда пользовательский запрос падает с \"incorrect checksum\".","caution":"Требуется модель FULL или BULK_LOGGED и непрерывная цепочка журналов. Восстановление страниц в онлайне — только Enterprise/Developer; в других редакциях база во время восстановления в офлайне. Страницы выделения (GAM/SGAM/PFS), заголовки файлов и загрузочная страница (1:9) так восстановить нельзя. 823/824 обычно означает проблему хранилища; не закрывайте инцидент без проверки диска/контроллера/SAN, иначе он повторится. При большом числе плохих страниц полное восстановление базы быстрее.","note":"suspect_pages хранит не более 1,000 строк; при заполнении новые ошибки не записываются, поэтому удаляйте старые строки (event_type 4/5/7). Несколько страниц перечисляются через запятую: PAGE = '1:336, 1:337'. В AG повреждённая страница на первичной реплике исправляется автоматически со вторичной путём автоматического восстановления страниц (sys.dm_hadr_auto_page_repair).","tags":["suspect_pages","page restore","823","824","corruption","bad page"]},{"id":"top-queries-cpu","task":"Самые дорогие запросы в кэше планов (CPU, чтения, длительность)","category":"Производительность","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":"Группирует команды в кэше планов по query_hash (ad-hoc запросы, различающиеся только литералами, сводятся в одну строку) и возвращает суммарные и средние CPU, длительность, логические чтения, предоставление памяти и число планов.","when":"Когда CPU загружен до предела, при жалобе \"сервер тормозит\", для баз без Query Store или когда нужен общесерверный обзор по всем базам.","caution":"Видит только планы, ещё находящиеся в кэше: запросы, чьи планы вытеснены перезапуском, DBCC FREEPROCCACHE, нехваткой памяти или RECOMPILE, отсутствуют. Для постоянного анализа по временным окнам используйте Query Store. plan_count > 1 намекает на чувствительность к параметрам или разные опции SET. При очень большом кэше планов запрос может занять несколько секунд; безопасно.","since":"2016","note":"total_grant_kb — столбец 2016+. Для итогов по хранимым процедурам используйте sys.dm_exec_procedure_stats.","tags":["dm_exec_query_stats","top queries","cpu","query_hash","plan cache","expensive query"]},{"id":"wait-stats-top","task":"Статистика ожиданий: чего ждёт сервер","category":"Производительность","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":"Отфильтровывает безобидные фоновые ожидания и раскладывает общее время ожиданий сервера по типам с процентом и долей resource vs. signal (очередь CPU); также показывает пользовательские запросы, ожидающие прямо сейчас.","when":"Как первый шаг анализа производительности для классификации узкого места (I/O, CPU, блокировки, журнал, память, параллелизм); для сравнения с базовой линией до/после.","caution":"Значения накапливаются с момента перезапуска; для текущей проблемы снимите два снимка и сравните разницу. DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR) сбрасывает счётчики для всего сервера и ломает инструменты мониторинга; избегайте этого. Интерпретация: PAGEIOLATCH_* = чтения с диска, WRITELOG = диск журнала, LCK_M_* = блокировки, CXPACKET/CXCONSUMER = параллелизм, RESOURCE_SEMAPHORE = предоставление памяти, SOS_SCHEDULER_YIELD + высокий signal = нехватка CPU. Запрос только на чтение; безопасно.","note":"Для ожиданий по сессиям используйте sys.dm_exec_session_wait_stats (2016+); для категорий ожиданий по запросам в Query Store — sys.query_store_wait_stats (2017+).","tags":["wait stats","dm_os_wait_stats","waits","pageiolatch","writelog","performance"]},{"id":"deadlock-system-health","task":"Чтение прошлых взаимоблокировок из сеанса system_health (deadlock graph)","category":"Производительность","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":"Читает события xml_deadlock_report из цели event_file сеанса Extended Events system_health (включён по умолчанию); для каждой взаимоблокировки возвращает время (UTC), процесс-жертву, число участников, базу данных и полный XML графа взаимоблокировки.","when":"Когда приложение сообщает об ошибке 1205, при анализе частоты взаимоблокировок и задействованных объектов; на серверах без флага трассировки 1222.","caution":"Файлы system_health перезаписываются по кругу; на нагруженном сервере старые взаимоблокировки могут пропасть, поэтому для постоянного архива настройте отдельный сеанс XE. Чтение многих файлов расходует CPU; в рабочие часы оставляйте TOP. События пишутся с задержкой, поэтому новая взаимоблокировка появляется через несколько секунд. Только чтение; безопасно.","note":"Щёлкните значение deadlock_graph и сохраните как .xdl, чтобы открыть его графически в SSMS. event_file сеанса system_health по умолчанию — 10 x 100 MB в 2016 SP2 CU12 / 2017 CU20 / 2019 CU3 и новее (Standard/Enterprise); 4 x 5 MB в старых сборках и других редакциях. Ключевые поля графа — inputbuf, waitresource (KEY/PAGE/OBJECT) и lockMode; большинство взаимоблокировок устраняется недостающим индексом или единым порядком доступа.","tags":["deadlock","system_health","xml_deadlock_report","extended events","1205","deadlock graph"]},{"id":"xe-long-query-session","task":"Создание сеанса Extended Events: запись медленных запросов в файл и их чтение","category":"Производительность","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":"Создаёт легковесный сеанс Extended Events, который пишет на диск команды и RPC-вызовы длительностью более 1 секунды с приложением, хостом, пользователем, базой и текстом SQL; превращает файлы .xel в строки средствами T-SQL.","when":"При жалобах на периодические замедления, когда нужны сведения о клиенте (app/host), которых не показывает Query Store, и как замена SQL Trace/Profiler.","caution":"Очень низкий порог (например, 0) быстро заполняет файлы и добавляет накладные расходы на нагруженном сервере; фильтруйте в предикате WHERE, а не после. Действие sql_text дорого при большом объёме. Папка должна быть доступна на запись учётной записи службы SQL Server. duration и cpu_time — в микросекундах. По завершении остановите/удалите сеанс; при STARTUP_STATE = OFF он не запускается после перезапуска.","note":"Чтобы ограничиться одной базой, добавьте sqlserver.database_name = N'Musteri' в условие WHERE. Для быстрого просмотра используйте SSMS > Management > Extended Events > Watch Live Data.","tags":["extended events","xe","event session","slow query","sql_statement_completed","event_file"]},{"id":"tempdb-contention","task":"Диагностика конкуренции и использования места в tempdb","category":"Производительность","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":"Показывает ожидания PAGELATCH на страницах выделения tempdb (PFS/GAM/SGAM) и системных таблицах, сессии, больше всего использующие tempdb (временные таблицы vs. сбросы sort/hash), какая база раздувает хранилище версий, и конфигурацию файлов.","when":"Когда растут ожидания PAGELATCH_UP/EX при интенсивной работе с временными таблицами, tempdb заполняется (1105/9002) или растёт хранилище версий.","caution":"Страницы 2:1:1, 2:1:2, 2:1:3 и страницы PFS с номерами, кратными 8088, означают конкуренцию за выделение: решение — несколько файлов данных одинакового размера с одинаковым ростом (по одному на ядро до 8, затем добавлять по 4). При конкуренции на страницах системных таблиц в 2019+ есть memory-optimized метаданные tempdb (требуется перезапуск). Если растёт хранилище версий, найдите самую старую открытую snapshot-транзакцию; сжатие tempdb — не решение. Запрос только на чтение; безопасно.","note":"Memory-optimized метаданные: ALTER SERVER CONFIGURATION SET MEMORY_OPTIMIZED TEMPDB_METADATA = ON; + перезапуск (2019+). В 2022 добавлены обновления GAM/SGAM без кратковременных блокировок (latch-free), что сильно снижает конкуренцию. Транзакция, удерживающая хранилище версий: 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":"Включение Query Store, рекомендуемые настройки и проверка состояния","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":"Включает Query Store в базе данных; начинает сохранять тексты запросов, планы, статистику выполнения и ожиданий по временным интервалам. Параметры задают лимит хранилища, очистку и режим захвата.","when":"Когда новая база вводится в эксплуатацию, перед обновлением версии/уровня совместимости (для базовой линии) или при подозрении на регрессию плана.","caution":"По достижении MAX_STORAGE_SIZE_MB Query Store молча переходит в READ_ONLY (readonly_reason = 65536) и перестаёт собирать данные; следите за current_storage_size_mb. QUERY_CAPTURE_MODE = ALL быстро заполняет хранилище и добавляет накладные расходы при интенсивной ad-hoc нагрузке. На очень нагруженных OLTP-системах могут появляться ожидания QDS_*. Выключение (OFF) сохраняет собранные данные; CLEAR удаляет их.","since":"2016","note":"В 2022 у новых баз Query Store по умолчанию включён в режиме READ_WRITE. В 2019+ QUERY_CAPTURE_MODE = CUSTOM принимает пороги (EXECUTION_COUNT, TOTAL_COMPILE_CPU_TIME_MS, TOTAL_EXECUTION_CPU_TIME_MS). Query Store для читаемых вторичных реплик (ALTER DATABASE ... FOR SECONDARY SET QUERY_STORE = ON) в 2022 — ограниченная предварительная версия, включается только флагом трассировки 12606 и не поддерживается на продакшене; в 2025 флаг не нужен (всё ещё preview).","tags":["query store","enable","max_storage_size_mb","query_capture_mode","read_only","qds"]},{"id":"qs-regressed-queries","task":"Поиск регрессий планов в Query Store (один запрос, разные планы)","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":"Находит запросы, выполнявшиеся более чем с одним планом за последние 24 часа; сопоставляет для каждого плана число выполнений, взвешенные средние длительность/CPU/чтения и во сколько раз он медленнее лучшего плана.","when":"Когда запрос \"вчера был быстрым, а сегодня медленный\", после обновления статистики, изменения индексов, повышения уровня совместимости или патча.","caution":"Средняя длительность зависит и от распределения данных; при сравнении планов, выполнявшихся с разными значениями параметров, смотрите и на avg_reads (parameter sniffing). Временные окна — в UTC. Для планов с малым числом выполнений результаты статистически слабы. Запрос только на чтение; безопасно.","since":"2016","note":"SSMS > Query Store > Regressed Queries делает то же графически. В 2022 при уровне совместимости 160 оптимизация Parameter Sensitive Plan (PSP) создаёт несколько вариантных query_id для одной команды; сопоставляйте их с родительским через sys.query_store_query_variant (parent_query_id). Автоматическая настройка 2017+ (FORCE_LAST_GOOD_PLAN) может исправлять такие регрессии автоматически.","tags":["query store","plan regression","regressed queries","parameter sniffing","query_store_runtime_stats","plan"]},{"id":"qs-force-plan-hints","task":"Принудительный хороший план или подсказка через 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":"Принудительно задаёт для выбранного запроса конкретный план Query Store; оптимизатор использует его при каждой компиляции. В 2022 можно также прикрепить подсказки, такие как RECOMPILE, MAXDOP или OPTIMIZE FOR, без изменения кода приложения.","when":"Когда регрессия плана требует срочного исправления на продакшене, пока не готово постоянное решение (индекс, переписывание запроса, статистика).","caution":"Принудительный план может деградировать по мере роста данных и не является постоянным решением; регулярно пересматривайте его. Если используемый планом индекс удалён/переименован, принудительное применение не срабатывает (last_force_failure_reason_desc = NO_INDEX и т. п.), и запрос компилируется обычным образом. После принудительного применения под новым plan_id может появиться \"морально эквивалентный\" план; это ожидаемо. Принудительное применение работает, пока Query Store в READ_ONLY, но проверяйте его после восстановления или отработки отказа AG.","since":"2016","note":"Подсказки Query Store — 2022+ (раньше в Azure SQL). Подсказки заменяют руководства планов; если подсказку нельзя применить, запрос выполняется без неё, а причина видна в last_query_hint_failure_reason_desc. Принудительные планы на читаемых вторичных репликах (@replica_group_id) требуют Query Store для вторичных реплик, что в 2022 — ограниченная предварительная версия, включаемая флагом трассировки 12606.","tags":["force plan","sp_query_store_force_plan","query store hints","plan forcing","regression","recompile"]},{"id":"missing-index-dmv","task":"Рекомендации по недостающим индексам из DMV (с оговорками)","category":"Обслуживание","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":"Оценивает рекомендации по недостающим индексам, записанные оптимизатором при компиляции, по использованию, оценочной стоимости и эффекту и генерирует черновик CREATE INDEX; в 2019+ показывает также запрос, стоящий за каждой рекомендацией.","when":"После нового релиза приложения, для быстрого обзора потребностей в индексах у запросов с преобладанием чтения или как стартовый список для ревью дизайна индексов.","caution":"Не применяйте рекомендации вслепую: оптимизатор игнорирует существующие индексы, часто выдаёт почти дублирующиеся (перекрывающиеся) рекомендации и слишком широкие списки INCLUDE и упорядочивает столбцы по равенству/неравенству, а не по селективности. Каждый новый индекс стоит записи и места; сначала рассмотрите расширение существующего индекса. DMV сбрасываются при перезапуске, переводе базы в офлайн и изменении индексов таблицы. Запрос только на чтение; безопасно.","note":"Перед созданием предложенного индекса сравните его с существующими индексами таблицы (sp_helpindex или sys.index_columns) и рассмотрите вместе с рецептом неиспользуемых индексов. Элемент MissingIndex в XML плана даёт ту же информацию по каждому запросу.","tags":["missing index","missing index dmv","dm_db_missing_index_details","index suggestion","create index","tuning"]},{"id":"unused-indexes","task":"Поиск неиспользуемых / только записываемых индексов","category":"Обслуживание","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":"Выводит некластеризованные индексы, которые не читались (seek/scan/lookup = 0) с момента перезапуска, но по-прежнему несут стоимость записи, с их размером и генерирует черновик DROP INDEX.","when":"При медленных INSERT/UPDATE на таблицах с интенсивной записью, для сокращения места и времени обслуживания (rebuild, CHECKDB) или перед консолидацией индексов.","caution":"Счётчики сбрасываются при перезапуске, переводе базы в офлайн/отсоединении и (в некоторых старых сборках 2012/2014) при перестроении индекса; индексы, используемые только отчётами конца месяца/года, в коротком окне выглядят \"неиспользуемыми\". Перед удалением наблюдайте хотя бы один полный бизнес-цикл, а в AG проверяйте использование на читаемых вторичных репликах отдельно (счётчики — на каждой реплике свои). Уникальные индексы обеспечивают ограничения, даже если не читаются; они исключены. Запрос только на чтение; безопасно.","note":"Отключение через ALTER INDEX ... DISABLE перед удалением упрощает откат (определение сохраняется; REBUILD возвращает индекс). Поиск имени индекса в Query Store или кэше планов показывает, упоминается ли он в подсказке.","tags":["unused index","write-only index","dm_db_index_usage_stats","drop index","write overhead","index"]},{"id":"stats-health","task":"Состояние статистики: когда обновлялась, сколько строк изменилось","category":"Обслуживание","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":"Показывает для каждой статистики время последнего обновления, долю выборки, число строк, изменённых после последнего обновления, и порог срабатывания автообновления (динамический порог для compat 130+).","when":"Для планов с большим разрывом оценочных и фактических строк, после крупной загрузки данных или при проверке эффективности задания обслуживания статистики.","caution":"Формула порога действует для уровня совместимости 130+ (MIN(500 + 20%, SQRT(1000 x rows))); на более низких уровнях действует правило 20% + 500. Никогда не обновлявшаяся статистика (пустые таблицы) возвращает NULL. Столбцам с перекосом и низким sample_pct может понадобиться FULLSCAN или PERSIST_SAMPLE_PERCENT. Запрос только на чтение; безопасно.","since":"2016","note":"persisted_sample_percent отражает UPDATE STATISTICS ... WITH PERSIST_SAMPLE_PERCENT = ON (2016 SP1 CU4 / 2017 CU1+). Команды обновления — в рецепте update-statistics.","tags":["statistics","stats health","dm_db_stats_properties","modification_counter","auto update","cardinality"]},{"id":"checkdb-strategy","task":"Стратегия DBCC CHECKDB: последняя успешная проверка, разбиение работы, интерпретация результатов","category":"Обслуживание","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":"Показывает время последнего успешного CHECKDB для каждой базы, оценивает нужный объём tempdb, позволяет разбить проверку больших баз на части CHECKALLOC + CHECKCATALOG + CHECKTABLE и возвращает результаты в виде строк (объект, индекс, страница).","when":"При аудите обслуживания, для очень больших баз, не укладывающихся в окно обслуживания, или для определения масштаба повреждения после 823/824.","caution":"Фраза \"repair_allow_data_loss is the minimum repair level\" в результате означает, что исправление удалит данные; сначала рассмотрите восстановление из исправной резервной копии (или восстановление страниц). Если затронуты только некластеризованные индексы (index_id > 1), удаление и повторное создание индекса устраняет проблему без потери данных. После обнаружения повреждения исследуйте дисковую подсистему. PHYSICAL_ONLY пропускает логические проверки; полный CHECKDB всё равно нужен периодически. При разбиении через CHECKTABLE покрытие неполное, если не выполняются также CHECKALLOC/CHECKCATALOG.","note":"DATABASEPROPERTYEX(..., 'LastGoodCheckDbTime') доступна в 2016 SP2, 2017 CU9 и 2019+; в старых сборках читайте dbi_dbccLastKnownGood из DBCC DBINFO WITH TABLERESULTS. Для разгрузки CHECKDB на копии, восстановленной из резервной копии (например, на тестовом сервере), также подтверждает целостность рабочей базы. Параметр MAXDOP — 2014 SP2 / 2016+.","tags":["dbcc checkdb","lastgoodcheckdbtime","checktable","corruption","repair_allow_data_loss","integrity"]},{"id":"resource-governor","task":"Ограничение отчётной нагрузки через Resource Governor (pool, workload group, classifier)","category":"Конфигурация","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":"При подключении направляет сессии определённых логинов или приложений в отдельную группу рабочей нагрузки; защищает OLTP-нагрузку, ограничивая для этой группы CPU, предоставление памяти, DOP и время CPU на запрос.","when":"Когда отчётные/BI-запросы душат OLTP, когда ETL пересекается с нагрузкой рабочих часов, или для разделения ресурсов на мультитенантном сервере.","caution":"Функция классификатора выполняется при каждом новом подключении; медленная или падающая функция замедляет или ломает каждый вход (в ней не должно быть запросов и обращений к таблицам). Если она работает неправильно, подключитесь через DAC и задайте CLASSIFIER_FUNCTION = NULL. Классификация происходит только при подключении; существующие сессии не затрагиваются. CAP_CPU_PERCENT — жёсткий предел, действующий даже при простаивающем CPU. MAX_CPU_PERCENT действует только при конкуренции за CPU. Resource Governor — возможность Enterprise/Developer.","note":"Превышение REQUEST_MAX_CPU_TIME_SEC по умолчанию только порождает событие (cpu_threshold_exceeded); с флагом трассировки 2422 (2016 SP2 / 2017 CU3+) запрос прерывается. Для ограничения I/O используйте MAX_IOPS_PER_VOLUME в пуле (2014+). Статистика: sys.dm_resource_governor_resource_pools / _workload_groups.","tags":["resource governor","resource pool","workload group","classifier","cpu limit","resource management"]},{"id":"tde-enable","task":"Настройка TDE: главный ключ, сертификат, резервная копия сертификата и ход шифрования","category":"Безопасность","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":"Создаёт в master главный ключ базы данных и сертификат, делает резервную копию сертификата с закрытым ключом, создаёт ключ шифрования базы данных AES-256 и шифрует файлы данных и журнала в состоянии покоя; показывает ход фонового сканирования шифрования.","when":"Для требований шифрования данных в состоянии покоя, таких как GDPR/PCI, и против кражи файлов резервных копий и файлов MDF/LDF.","caution":"Если утеряна резервная копия сертификата + закрытого ключа или её пароль, TDE-база и все её резервные копии НАВСЕГДА становятся нечитаемыми; храните копию в защищённом хранилище вдали от сервера. Включение TDE на любой базе шифрует также tempdb (небольшие затраты CPU на весь сервер). Сканирование шифрования интенсивно по I/O; в 2019+ его можно приостановить через ALTER DATABASE ... SET ENCRYPTION SUSPEND/RESUME. Для AG/log shipping сертификат сначала нужно установить на каждом целевом сервере. TDE не шифрует данные в памяти и в сети.","note":"Истечение срока сертификата не останавливает работающее TDE, но для ротации создайте новый сертификат и перешифруйте через ALTER DATABASE ENCRYPTION KEY ENCRYPTION BY SERVER CERTIFICATE [new] (перешифровывается только DEK, а не данные). encryption_state_desc и encryption_scan_state_desc — столбцы 2019+; в старых версиях используйте encryption_state (3 = зашифровано, 2 = в процессе). TDE доступно в редакции Standard начиная с 2019.","tags":["tde","transparent data encryption","certificate","backup certificate","encryption","dek"]},{"id":"sql-server-audit","task":"Настройка SQL Server Audit: входы, изменения разрешений и доступ к таблицам","category":"Безопасность","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":"Создаёт аудит сервера с записью в файл; фиксирует неудачные входы, членство в ролях, изменения участников и разрешений на уровне сервера и доступ SELECT/DML к выбранной таблице на уровне базы, а также читает записи средствами T-SQL.","when":"Для требований аудита/соответствия (GDPR, PCI, SOX), для вопросов \"кто читал/изменял эту таблицу\" или при подозрении на повышение привилегий.","caution":"Аудит SELECT на нагруженной таблице генерирует записи очень быстро и снижает производительность; сужайте его по объекту и участнику. ON_FAILURE = SHUTDOWN останавливает сервер, если аудит не удаётся записать; используйте только по требованию. Папка аудита должна быть доступна на запись учётной записи службы SQL Server и защищена от удаления не-DBA. event_time — в UTC. Спецификация аудита базы данных восстанавливается вместе с базой, но остаётся потерянной (orphaned), если GUID аудита сервера не совпадает.","note":"Спецификации аудита базы данных доступны в редакции Standard начиная с 2016 SP1. Всегда включайте AUDIT_CHANGE_GROUP, чтобы фиксировались изменения самого аудита, включая его отключение.","tags":["sql server audit","server audit","audit specification","fn_get_audit_file","auditing","compliance"]},{"id":"orphaned-users-fix","task":"Поиск потерянных пользователей и их привязка к логинам (после восстановления / AG)","category":"Безопасность","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":"Находит пользователей базы данных, чей SID не соответствует ни одному логину сервера (orphaned); привязывает пользователя к существующему логину и генерирует скрипт для создания логинов на другом сервере с тем же SID и хешем пароля.","when":"После восстановления/присоединения базы на другом сервере, когда приложение получает \"Login failed for user\" после отработки отказа AG/log shipping, или при миграции сервера.","caution":"ALTER USER ... WITH LOGIN меняет SID пользователя на SID логина; в AG это изменение переходит с первичной реплики на вторичные, но если у логинов на вторичных серверах другие SID, проблема вернётся при следующей отработке отказа. Постоянное решение — создавать логины с одинаковым SID на каждой реплике. Хеш пароля — чувствительные данные; храните скрипт в безопасности. Пользователям автономной базы данных (authentication_type = DATABASE) логин не нужен, и они не выводятся.","note":"sp_change_users_login устарела; используйте ALTER USER WITH LOGIN. Для потерянного dbo (нет логина владельца базы) используйте ALTER AUTHORIZATION ON DATABASE::[Musteri] TO [sa];. Для массовой миграции логинов используйте 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":"Аудит логинов и разрешений: члены sysadmin, рискованные разрешения, слабые пароли","category":"Безопасность","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":"Выводит членство в серверных ролях, рискованные разрешения, такие как CONTROL SERVER/IMPERSONATE, SQL-логины с выключенной политикой паролей, заблокированные или с паролем, равным имени логина либо пустым; в базе — членство в ролях и явные разрешения на несистемные объекты.","when":"Для периодических ревизий доступа, перед аудитом, после ухода сотрудника из команды или при подозрении на инцидент безопасности.","caution":"Разрешения, выданные через группы Windows, отображаются под именем группы; чтобы увидеть людей, используйте EXEC xp_logininfo N'DOMAIN\\Group', 'members';. Перед отзывом чего-либо проверьте зависимости приложений. PWDCOMPARE работает только для SQL-логинов и требует CONTROL SERVER. Запрос только на чтение; безопасно.","note":"Эффективные разрешения участника: EXECUTE AS USER = N'app_user'; SELECT * FROM fn_my_permissions(NULL, 'DATABASE'); REVERT;. В 2022 новые гранулярные роли (##MS_ServerStateReader##, ##MS_DefinitionReader## и т. п.) дают права мониторинга без sysadmin.","tags":["permission audit","sysadmin","server_role_members","database_permissions","pwdcompare","security"]},{"id":"ledger-tables","task":"Создание ledger-таблицы, чтение истории изменений и проверка целостности","category":"Безопасность","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":"Создаёт ledger-таблицу, которая записывает изменения строк в криптографическую цепочку хешей; представление _Ledger показывает, кто и когда выполнил каждый INSERT/UPDATE/DELETE, а проверка по сохранённому дайджесту доказывает, что данные не изменялись задним числом (в том числе DBA).","when":"Для данных, неизменность которых должна быть доказуема: финансовые записи, журналы аудита, записи о договорах/согласованиях.","caution":"Ledger нельзя отключить после создания таблицы; ledger-таблицу нельзя превратить в обычную, а удалённая ledger-таблица сохраняется как dropped ledger table. Обновляемый ledger пишет строку истории при каждом изменении, и объём хранилища растёт. Если дайджесты не хранятся вне базы в неизменяемом хранилище (WORM, Azure immutable blob), проверка не имеет доказательной силы. sp_verify_database_ledger завершается ошибкой 37498 без snapshot isolation.","since":"2022","note":"sp_generate_database_ledger_digest возвращает JSON; sp_verify_database_ledger принимает один дайджест или массив JSON и может проверить одну таблицу через @table_name. Для автоматического хранения дайджестов: ALTER DATABASE SCOPED CONFIGURATION SET LEDGER_DIGEST_STORAGE_ENDPOINT = '<blob url>'. CREATE DATABASE ... WITH LEDGER = ON делает каждую таблицу ledger-таблицей по умолчанию.","tags":["ledger","append-only","sp_verify_database_ledger","digest","immutable","tamper-evident"]},{"id":"agent-job-failures","task":"История сбоев заданий SQL Agent и статус последнего запуска","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":"Показывает шаги заданий, завершившиеся сбоем или отменённые за последние 24 часа, с сообщением об ошибке; последний результат каждого задания, выполняется ли оно сейчас и следующий запуск по расписанию; а также состояние службы Agent.","when":"При утренней проверке, при оповещении о задании резервного копирования/обслуживания, для вопроса \"отработало ли ночное задание\" или после отработки отказа, чтобы убедиться, что задания выполняются на нужной реплике.","caution":"История заданий в msdb ограничена (по умолчанию всего 1,000 строк, 100 на задание); часто выполняемые задания быстро вытесняют старые сбои, поэтому увеличьте лимит в свойствах Agent. Столбец message обрезается до 4,000 символов; для длинного вывода задайте выходной файл на шаге задания. Если служба Agent остановлена, ни одно запланированное задание не выполняется и ни одно не сообщает об ошибке. Запрос только на чтение; безопасно.","note":"msdb.dbo.agent_datetime не документирована, но широко используется. В AG задания должны существовать на каждой реплике и проверять роль на первом шаге через 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":"Добавление участника в набор реплик","category":"Replica Set","command":"rs.add({ host: \"mongo2:27017\", priority: 1, votes: 1 })","what":"Добавляет нового участника (secondary) в существующий набор реплик и запускает начальную синхронизацию.","when":"При добавлении нового узла для увеличения производительности чтения или для избыточности.","caution":"Новый участник забирает данные с primary во время начальной синхронизации; при больших объёмах данных это создаёт нагрузку на сеть и диск.","tags":["replica","rs.add","member","member","add"]},{"id":"rs-remove-member","task":"Удаление участника из набора реплик","category":"Replica Set","command":"rs.remove(\"mongo2:27017\")","what":"Удаляет указанный хост из конфигурации набора реплик.","when":"При выводе узла из эксплуатации или замене оборудования.","caution":"Не удаляйте primary; удаление участников с нарушением большинства может сделать набор недоступным для чтения и записи.","tags":["replica","rs.remove","member","member","remove"]},{"id":"rs-step-down","task":"Понижение primary (stepDown)","category":"Replica Set","command":"rs.stepDown(60)","what":"Понижает текущий primary до secondary на указанное число секунд и запускает новые выборы.","when":"Чтобы передать роль primary другому узлу перед плановым обслуживанием.","caution":"Во время stepDown происходит кратковременный перерыв записи; приложение должно переподключиться.","tags":["replica","stepdown","primary","failover","step down"]},{"id":"rs-set-priority","task":"Изменение приоритета участника","category":"Replica Set","command":"cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg)","what":"Меняет приоритет указанного участника на избрание primary.","when":"Когда нужно, чтобы определённый узел был предпочтительным primary.","caution":"reconfig может запустить выборы и вызвать кратковременную смену primary/перерыв записи.","tags":["replica","priority","reconfig","priority","election"]},{"id":"rs-status","task":"Просмотр состояния набора реплик","category":"Replica Set","command":"rs.status()","what":"Показывает состояние, роли, отставание репликации и здоровье участников набора.","when":"При проверке здоровья и отставания репликации.","caution":"Команда только для чтения; без риска.","tags":["replica","status","status","health","rs.status"]},{"id":"rs-hidden-member","task":"Настройка скрытого / неголосующего участника","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":"Делает участника скрытым (невидимым для приложения), неголосующим и с приоритетом 0, используя его только для резервного копирования/аналитики.","when":"Когда нужно изолировать бэкапы или аналитические запросы.","caution":"Скрытый участник должен иметь приоритет 0; иначе reconfig завершается ошибкой.","tags":["replica","hidden","votes","hidden","non-voting"]},{"id":"rs-resync","task":"Повторная синхронизация участника с нуля","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 нет отдельной команды resync; остановка secondary, очистка его каталога данных и перезапуск запускают новую начальную синхронизацию с primary (или источника синхронизации).","when":"Когда участник отстал настолько, что вышел за окно oplog (RECOVERING / too stale), или его данные стали несогласованными.","caution":"Все локальные данные участника удаляются и загружаются заново; делайте это только на secondary и убедитесь, что большинство остаётся доступным. Время синхронизации и нагрузка на сеть/диск высоки; для очень больших объёмов быстрее начальное заполнение из снимка файловой системы здорового участника.","tags":["replica","resync","initialsync","sync","resync"]},{"id":"sh-add-shard","task":"Добавление шарда в кластер","category":"Шардинг","command":"sh.addShard(\"shardRS/mongoShard1:27018\")","what":"Добавляет новый шард (набор реплик) в шардированный кластер.","when":"При горизонтальном масштабировании ёмкости хранения или пропускной способности.","caution":"После добавления шарда балансировщик начинает переносить чанки; это создаёт дополнительную нагрузку IO.","tags":["sharding","addshard","shard","add","cluster"]},{"id":"sh-shard-collection","task":"Шардирование коллекции","category":"Шардинг","command":"sh.shardCollection(\"shop.customer\", { customerId: \"hashed\" })","what":"Шардирует указанную коллекцию по заданному ключу шардирования.","when":"Когда нужно распределить растущую коллекцию по нескольким шардам.","caution":"Ключ шардирования можно изменить через reshardCollection в 5.0+ (и расширить через refineCollectionShardKey в 4.4+), но это дорого, так как перераспределяются все данные; неудачный выбор ключа ведёт к неравномерному распределению.","tags":["sharding","shardcollection","key","customer","shard key"]},{"id":"sh-start-balancer","task":"Запуск балансировщика","category":"Шардинг","command":"sh.startBalancer()","what":"Включает процесс балансировщика, распределяющий чанки.","when":"Когда нужно снова включить балансировку после обслуживания.","caution":"Балансировщик при переносе чанков создаёт нагрузку на IO и сеть; задайте окно балансировки вне часов пик.","tags":["sharding","balancer","start","start","rebalance"]},{"id":"sh-stop-balancer","task":"Остановка балансировщика","category":"Шардинг","command":"sh.stopBalancer()","what":"Отключает балансировщик, выполняющий перенос чанков.","when":"Чтобы остановить миграции перед резервным копированием, обслуживанием или массовой загрузкой данных.","caution":"Если он долго остаётся выключенным, шарды становятся несбалансированными; не забудьте потом снова его включить.","tags":["sharding","balancer","stop","stop","maintenance"]},{"id":"sh-status","task":"Просмотр состояния шардирования","category":"Шардинг","command":"sh.status()","what":"Сводка по шардам, базам данных, шардированным коллекциям и распределению чанков.","when":"При анализе состояния распределения кластера и здоровья балансировщика.","caution":"Команда только для чтения; без риска.","tags":["sharding","status","status","chunk","sh.status"]},{"id":"sh-add-zone","task":"Добавление зоны (тега) и назначение диапазона","category":"Шардинг","command":"sh.addShardToZone(\"shard0000\", \"TR\"); sh.updateZoneKeyRange(\"shop.customer\", { region: \"TR\" }, { region: \"TR~\" }, \"TR\")","what":"Назначает шард зоне и привязывает к этой зоне определённый диапазон ключей (zone sharding).","when":"Когда нужно держать данные на определённых шардах по географии или оборудованию.","caution":"Диапазоны зон должны соответствовать ключу шардирования; неверный диапазон мешает переносу чанков.","tags":["sharding","zone","tag","region","range"]},{"id":"sh-move-chunk","task":"Ручной перенос чанка","category":"Шардинг","command":"sh.moveChunk(\"shop.customer\", { customerId: 1000 }, \"shard0001\")","what":"Вручную переносит чанк, содержащий указанный ключ, на целевой шард.","when":"Для исправления неравномерного распределения или размещения определённых данных.","caution":"Ручной перенос может конфликтовать с балансировщиком; перенос больших чанков создаёт нагрузку IO и кратковременные блокировки.","tags":["sharding","movechunk","chunk","move","manual"]},{"id":"compact-collection","task":"Уплотнение коллекции (compact)","category":"Обслуживание","command":"db.runCommand({ compact: \"customer\" })","what":"Переписывает коллекцию и её индексы на диске, освобождая свободное место.","when":"Когда нужно вернуть место на диске после множества удалений/обновлений.","caution":"В 4.4+ compact не блокирует чтение/запись; он блокирует только некоторые DDL-операции над той же коллекцией (drop, createIndex, dropIndex и т. п.); тем не менее он создаёт интенсивный IO и не всегда возвращает место операционной системе. По возможности запускайте на secondary и в окно обслуживания.","tags":["compact","storage","maintenance","disk","storage"]},{"id":"create-index-background","task":"Создание индекса (без блокировки)","category":"Обслуживание","command":"db.customer.createIndex({ email: 1 }, { name: \"idx_email\" })","what":"Создаёт индекс по указанному полю; начиная с MongoDB 4.2 построение индекса по умолчанию не блокирует запись.","when":"При добавлении нового индекса для повышения производительности запросов.","caution":"На больших коллекциях построение индекса потребляет CPU и IO; нагрузка на primary может вырасти.","since":"MongoDB 4.2","note":"В MongoDB 4.2 появился новый процесс построения индексов, и создание индекса по умолчанию больше не блокирует запись; поэтому старая опция { background: true } не нужна и начиная с 4.2 игнорируется.","tags":["index","createindex","background","create","performance"]},{"id":"collection-stats","task":"Размеры индексов и коллекций","category":"Обслуживание","command":"db.customer.stats({ indexDetails: true })","what":"Сообщает размер коллекции, размер хранилища и размер каждого индекса.","when":"При анализе использования хранилища и стоимости индексов.","caution":"Команда только для чтения; без риска.","tags":["stats","index","size","collection","storage"]},{"id":"log-rotate","task":"Ротация файла журнала","category":"Обслуживание","command":"db.adminCommand({ logRotate: 1 })","what":"Закрывает текущий файл журнала и открывает новый (ротация журнала).","when":"Когда файл журнала вырос или перед архивированием.","caution":"Если используется вместе с ротацией журналов ОС, проверьте конфигурацию, чтобы избежать конфликтов.","tags":["log","logrotate","rotate","maintenance","archive"]},{"id":"current-op-long","task":"Поиск долгих операций","category":"Обслуживание","command":"db.currentOp({ \"active\": true, \"secs_running\": { $gt: 5 } })","what":"Выводит активные операции, выполняющиеся дольше 5 секунд.","when":"При поиске проблемных запросов во время замедлений или зависаний.","caution":"Команда только для чтения; без риска.","tags":["currentop","long","long-running","operation","diagnostics"]},{"id":"kill-op","task":"Завершение операции","category":"Обслуживание","command":"db.killOp(12345)","what":"Завершает выполняющуюся операцию с указанным opid.","when":"Когда запрос блокирует систему или выполняется слишком долго.","caution":"Завершение операции записи может оставить частичные изменения; не завершайте системные операции и операции репликации.","tags":["killop","kill","operation","kill","diagnostics"]},{"id":"set-parameter","task":"Изменение параметра времени выполнения","category":"Параметры","command":"db.adminCommand({ setParameter: 1, cursorTimeoutMillis: 600000 })","what":"Меняет параметр времени выполнения без перезапуска сервера.","when":"Когда нужно временно изменить поведение сервера.","caution":"Изменение не сохраняется; после перезапуска возвращается значение из файла конфигурации. Неверное значение может снизить производительность.","tags":["setparameter","parameter","runtime","setting","admin"]},{"id":"set-profiling","task":"Установка уровня профилирования","category":"Параметры","command":"db.setProfilingLevel(1, { slowms: 100 })","what":"Записывает медленные запросы (дольше 100 ms) в коллекцию system.profile (уровень 1).","when":"Когда нужно диагностировать медленные запросы.","caution":"Уровень 2 записывает все операции и добавляет значительные накладные расходы; данные профилирования занимают место на диске.","tags":["profiling","slowms","profiler","slow","diagnostics"]}]},"recipes/mongodb-ext.json":{"engineId":"mongodb","engine":"MongoDB","order":2,"recipes":[{"id":"rs-oplog-window","task":"Измерение окна oplog и отставания репликации","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":"Показывает, на сколько часов записи хватает oplog (окно oplog) и на сколько секунд каждый secondary отстаёт от primary, вместе с его источником синхронизации.","when":"При оповещении об отставании репликации, перед плановым обслуживанием для расчёта того, сколько участник может быть недоступен, и после массовых заданий записи.","caution":"Если участник недоступен дольше окна oplog, он становится 'too stale' и требует начальной синхронизации. Массовые задания update/delete могут сжать окно за несколько часов; оценивайте окно по его значению при пиковой нагрузке. Расчёт отставания основан на разнице optimeDate; если primary не получает записей, отставание не выглядит большим, но и окно не сокращается.","since":"MongoDB 4.4","note":"rs.printSlaveReplicationInfo() в 4.4 переименована в rs.printSecondaryReplicationInfo(). Если окно слишком мало, увеличьте oplog через replSetResizeOplog (см. rs-oplog-resize). Отложенные участники отстают на secondaryDelaySecs (slaveDelay в 4.4); исключите их из оповещений об отставании.","tags":["oplog","oplog window","replication lag","delay","secondary","printReplicationInfo"],"untested":true},{"id":"rs-oplog-resize","task":"Изменение размера oplog онлайн (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":"Увеличивает или уменьшает oplog работающего участника без перезапуска; minRetentionHours сохраняет записи oplog в течение минимального периода, даже если лимит размера превышен.","when":"Когда окно oplog не покрывает длительность обслуживания или начальной синхронизации либо массовые задания записи сжимают окно.","caution":"Команда влияет только на участника, где выполняется, и не реплицируется; применяйте её на каждом участнике. Окна должно хватать и на участниках, служащих источниками синхронизации. Уменьшение не сразу возвращает место на диске ОС; для этого нужен compact на oplog.rs. С minRetentionHours oplog может вырасти сверх size в зависимости от объёма записи; планируйте диск соответственно.","since":"MongoDB 4.4","note":"Новый размер сохраняется после перезапуска; minRetentionHours, заданный командой, — нет, поэтому для сохранения задайте также storage.oplogMinRetentionHours. size должен быть не менее 990 MB. Изменение replication.oplogSizeMB в конфигурации действует только при первом создании oplog.","tags":["oplog","resize","replSetResizeOplog","minRetentionHours","size","oplog window"],"untested":true},{"id":"rs-force-reconfig","task":"Принудительная реконфигурация набора реплик после потери большинства (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":"Когда большинство голосующих участников потеряно безвозвратно (например, пропал целый ЦОД), строит новую конфигурацию из уцелевших участников, чтобы набор снова мог избрать primary.","when":"Только в сценарии катастрофы: большинство не вернётся, и набор застрял в режиме только для чтения.","caution":"Это крайняя мера. Если потерянные участники позже вернутся со старой конфигурацией, возможны split-brain и rollback; изолируйте их от сети и при возвращении очистите их данные и добавьте через начальную синхронизацию. Выберите участника с самым свежим optime; иначе записи, не подтверждённые большинством на других участниках, попадут в rollback. Не используйте force для рутинных изменений состава.","since":"MongoDB 4.4","note":"force reconfig увеличивает версию конфигурации на большое случайное значение и является единственной формой reconfig, которую можно выполнить на secondary. В 4.4+ обычные reconfig могут добавлять или удалять не более одного голосующего участника за раз; на force это ограничение не распространяется.","tags":["force reconfig","majority","disaster","recovery","split-brain","rs.reconfig"],"untested":true},{"id":"rs-rolling-maintenance","task":"Поочерёдное обслуживание набора реплик (перезапуск, патч ОС, минорное обновление)","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":"Задаёт порядок вывода участников в обслуживание по одному, сначала secondary и в конце primary, чтобы перезапуски, патчи ОС или обновления бинарников выполнялись без вывода набора из работы.","when":"При плановых обновлениях ОС/ядра, минорных обновлениях версии, изменении параметров mongod или обслуживании дисков/оборудования.","caution":"Никогда не останавливайте больше одного участника за раз; в наборе из 3 участников потеря двух означает потерю большинства. После каждого участника убедитесь в состоянии SECONDARY и в том, что отставание устранено. stepDown вызывает кратковременный перерыв записи; убедитесь, что драйверы переподключаются с retryable writes. Если есть арбитр, обработайте его в той же последовательности.","since":"MongoDB 4.4","note":"rs.stepDown(stepDownSecs, secondaryCatchUpPeriodSecs): второй параметр — сколько primary ждёт, пока secondary догонит; если никто не догнал, stepDown завершается ошибкой, и primary остаётся. Чтобы определённый участник не стал primary во время обслуживания, выполните на нём rs.freeze(seconds).","tags":["rolling","maintenance","restart","stepDown","upgrade","patching"],"untested":true},{"id":"sh-balancer-window","task":"Состояние балансировщика, активное окно и балансировка по коллекциям","category":"Шардинг","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":"Показывает, включён ли балансировщик и выполняет ли раунд, ограничивает перенос чанков временным окном и отключает или включает балансировку для отдельной коллекции.","when":"Когда перенос чанков мешает дневной производительности, во время окна резервного копирования или при изоляции проблемы миграции на одной коллекции.","caution":"Слишком узкое окно не успевает за ростом данных, и шарды разбалансируются; регулярно проверяйте баланс через balancerCollectionStatus. Миграция, начатая до закрытия окна, выполняется до конца. Если балансировка коллекции оставлена выключенной, эта коллекция продолжает расти на одном шарде.","since":"MongoDB 6.0","note":"Время activeWindow вычисляется в локальном часовом поясе primary набора реплик config server. В 6.0.3+ балансировщик принимает решения по разнице объёма данных между шардами, а не по числу чанков; число чанков в sh.status() может выглядеть неравномерным, хотя данные сбалансированы.","tags":["balancer","activeWindow","window","migration","sharding","balancerCollectionStatus"],"untested":true},{"id":"sh-jumbo-chunks","task":"Поиск jumbo-чанков, их разделение и снятие флага","category":"Шардинг","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":"Выводит чанки с флагом jumbo, которые балансировщик не смог перенести, разделяет те, которые можно разделить, и снимает флаг jumbo; показывает, как уточнить ключ шардирования для огромных чанков с одним значением.","when":"Когда один шард остаётся заметно больше других, в выводе sh.status() появляется jumbo или балансировщик сообщает об ошибках миграции.","caution":"Чанк, все документы которого имеют одно значение ключа шардирования, разделить нельзя; единственное долговременное решение — изменить ключ шардирования через refineCollectionShardKey или reshardCollection. refineCollectionShardKey требует заранее существующего индекса для нового ключа. Снятие флага jumbo без разделения заставляет балансировщик повторять попытку с тем же чанком и снова падать.","since":"MongoDB 5.0","note":"Ключи шардирования с низкой кардинальностью или сосредоточенные на одном значении (например, status, country) — самая частая причина jumbo-чанков. До 5.0 документы config.chunks запрашиваются по полю ns. clearJumboFlag появился в 4.2.3/4.0.15.","tags":["jumbo","chunk","split","clearJumboFlag","shard key","refineCollectionShardKey"],"untested":true},{"id":"sh-shard-key-check","task":"Проверка выбора ключа шардирования и распределения данных (getShardDistribution, analyzeShardKey)","category":"Шардинг","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":"Показывает процент данных коллекции на каждом шарде; analyzeShardKey сообщает кардинальность, самые частые значения и монотонность текущего или кандидатного ключа шардирования, а также нацелены ли выбранные запросы на один шард или на все шарды (scatter-gather).","when":"Перед шардированием новой коллекции, при замеченном горячем шарде или неравномерном росте и перед решением о reshardCollection.","caution":"analyzeShardKey требует индекса, поддерживающего ключ, и на больших коллекциях делает выборку документов; в пиковое время это добавляет нагрузку на чтение. Пока выборка запросов включена, образцы пишутся в config.sampledQueries; выключите её по завершении анализа. Монотонно возрастающий ключ (ObjectId, timestamp) направляет все вставки на один шард.","since":"MongoDB 7.0","note":"analyzeShardKey и configureQueryAnalyzer появились в 7.0 и работают также на наборах реплик. Хороший ключ шардирования имеет высокую кардинальность, низкую частоту, не монотонен и встречается в фильтре частых запросов. Хешированный ключ исправляет распределение, но превращает запросы по диапазону в scatter-gather.","tags":["shard key","analyzeShardKey","getShardDistribution","cardinality","distribution","hot shard"],"untested":true},{"id":"explain-execstats","task":"Анализ плана запроса с реальной статистикой выполнения (explain executionStats)","category":"Производительность","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":"Выполняет запрос и показывает выигравший план (IXSCAN/COLLSCAN, стадия SORT), число возвращённых документов, просмотренных ключей индекса и документов, а также затраченное время.","when":"При анализе запроса, выглядящего медленным в профилировщике или журнале, и при проверке того, что новый индекс действительно используется.","caution":"executionStats реально выполняет запрос; explain тяжёлого запроса в пиковое время тоже добавляет нагрузку. Для команд записи (update/delete/findAndModify) explain не изменяет данные, но выполняет план. Высокое соотношение totalDocsExamined/nReturned или стадия SORT в памяти в плане означают, что индекс не подходит.","since":"MongoDB 4.4","note":"При проектировании составных индексов применяйте правило ESR: сначала поля равенства (Equality), затем сортировки (Sort), затем диапазона (Range). Используйте explain(\"allPlansExecution\"), чтобы увидеть и отвергнутые планы. В 7.0+, когда запрос выполняется на Slot-Based Engine, winningPlan содержит поля queryPlan и slotBasedPlan.","tags":["explain","executionStats","IXSCAN","COLLSCAN","plan","index"]},{"id":"profiler-analyze","task":"Извлечение самых медленных запросов и форм запросов из данных профилировщика","category":"Производительность","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":"По записям коллекции system.profile выводит недавние медленные операции, суммарное время по форме запроса (queryHash) и запросы COLLSCAN без индекса.","when":"При жалобе на медленную работу, чтобы найти, какой запрос потребляет время сервера, и определить потребность в индексах.","caution":"system.profile по умолчанию — ограниченная (capped) коллекция размером 1 MB, своя в каждой базе; на нагруженной системе записи перезаписываются за минуты, поэтому анализируйте, пока профилирование включено, или сразу после. Уровень профилирования 2 или slowms 0 записывает каждую операцию и добавляет значительные накладные расходы. Записи хранятся только на участнике, где выполнялась операция, и не реплицируются.","since":"MongoDB 4.4","note":"Даже при выключенном профилировании операции дольше slowms (по умолчанию 100 ms) пишутся в журнал mongod как 'Slow query'; attr.durationMillis и attr.planSummary в журнале дают ту же диагностику. В 8.0+ для формы запроса пишется также поле planCacheShapeHash.","tags":["profiler","system.profile","slow query","top queries","COLLSCAN","queryHash"]},{"id":"index-usage-hide","task":"Поиск неиспользуемых индексов и их скрытие перед удалением (hideIndex)","category":"Производительность","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 показывает, сколько раз использовался каждый индекс; hideIndex скрывает индекс от планировщика запросов без удаления, поэтому эффект удаления можно проверить обратимо.","when":"При чистке ненужных индексов, добавляющих накладные расходы на запись и место.","caution":"Счётчики свои на каждом участнике и сбрасываются при перезапуске; если чтение идёт на secondary, проверьте всех участников и наблюдайте достаточно долго (включая отчёты конца месяца). Уникальные или TTL-индексы с ops 0 всё ещё могут быть нужны: уникальное ограничение и удаления TTL не отражаются в счётчике. Скрытый индекс по-прежнему поддерживается при записи, а скрытый уникальный индекс по-прежнему обеспечивает ограничение.","since":"MongoDB 4.4","note":"hideIndex требует featureCompatibilityVersion 4.4 или выше. Индекс _id скрыть нельзя. Отмена скрытия через unhideIndex не требует перестроения и действует сразу.","tags":["indexStats","hideIndex","unused index","index cleanup","dropIndex","index"]},{"id":"connections-by-app","task":"Подсчёт соединений по приложениям и клиентам (диагностика шторма подключений)","category":"Производительность","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":"Показывает общее и доступное число соединений на сервере, затем группирует открытые соединения по appName и IP клиента с указанием, сколько из них активны.","when":"Когда число соединений внезапно скачет, при ошибках 'connection refused' или таймаутах пула, чтобы найти, какой сервер приложений допускает утечку соединений.","caution":"idleConnections: true возвращает также все простаивающие соединения, поэтому на системах с тысячами соединений вывод большой; поэтому важно сводить его через $group. Строки с пустым client — внутренние потоки. Каждое соединение потребляет примерно 1 MB памяти; шторм подключений увеличивает и память вне кэша WiredTiger.","since":"MongoDB 4.4","note":"Долговременное решение обычно на стороне драйвера: maxPoolSize, maxIdleTimeMS и число экземпляров приложения. Если приложения не задают appName, различить их трудно; попросите указывать appName=<service> в строке подключения. net.maxIncomingConnections и значение ulimit -n в ОС задают жёсткий верхний предел.","tags":["connections","connection count","currentOp","appName","connection storm","pool"]},{"id":"index-build-monitor","task":"Мониторинг и прерывание выполняющегося построения индекса","category":"Обслуживание","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":"Выводит выполняющиеся построения индексов с числом просмотренных документов и процентом выполнения (progress.done / progress.total); при необходимости прерывает построение через dropIndex.","when":"Чтобы оценить, когда завершится createIndex, запущенный на большой коллекции, или остановить построение, создающее нагрузку в пиковое время.","caution":"Прерывайте построение индекса через dropIndex/dropIndexes, а не killOp; в наборе реплик выполняйте на primary, чтобы прерывание распространилось на всех участников. Некоторые DDL-операции над коллекцией ждут во время построения. В конце построения берётся кратковременная эксклюзивная блокировка; при очень интенсивной записи этот момент может вызвать задержки.","since":"MongoDB 4.4","note":"В наборах реплик 4.4+ построение индекса идёт одновременно на всех участниках с данными и фиксируется, когда выполнен commitQuorum (по умолчанию votingMembers); если secondary отстаёт, построение не завершается и на primary. В 5.0+ построение, прерванное штатной остановкой, после перезапуска продолжается с того же места.","tags":["index build","createIndex","progress","monitor","dropIndex","commitQuorum"]},{"id":"rolling-index-build","task":"Поочерёдное построение индекса в наборе реплик (участник за участником)","category":"Обслуживание","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":"Строит индекс на каждом участнике отдельно, временно запуская его как standalone вне набора; primary никогда не несёт нагрузку построения.","when":"Когда влияние распределённого построения по умолчанию на primary и на отставание репликации неприемлемо для очень большой коллекции.","caution":"Время обслуживания каждого участника должно быть короче окна oplog; иначе участник при возвращении окажется too stale. При поочерёдном построении уникального индекса остановите запись в коллекцию на всё время построения; иначе на некоторых участниках могут оказаться документы, нарушающие ограничение. Запускайте standalone-участника на другом порту, чтобы к нему не подключались приложения.","since":"MongoDB 4.4","note":"В 4.4+ построение по умолчанию уже не блокирует и идёт одновременно на всех участниках; поочерёдный метод нужен, только если эта нагрузка неприемлема. В шардированном кластере процедура выполняется для каждого набора реплик шарда отдельно, с предварительно остановленным балансировщиком.","tags":["rolling index","index build","standalone","replica","createIndex","maintenance"],"untested":true},{"id":"ttl-index","task":"Создание TTL-индекса, изменение срока хранения и мониторинг удалений TTL","category":"Обслуживание","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":"Создаёт TTL-индекс, автоматически удаляющий документы через заданное время после значения поля даты, меняет срок через collMod без перестроения и показывает, сколько документов удалил TTL-монитор.","when":"Чтобы очищать данные с ограниченным сроком жизни, такие как сессии, журналы или временные токены, без задания удаления в приложении, и чтобы выяснить, почему растущая коллекция не уменьшается.","caution":"TTL-монитор по умолчанию запускается каждые 60 секунд; удаление не мгновенное. Поле должно иметь тип Date (или быть массивом Date); даты в виде строк или документы без поля никогда не удаляются. Сокращение срока приводит к удалению множества документов сразу, нагрузке на запись и отставанию репликации. Удаления выполняются только на primary и пишутся в oplog.","since":"MongoDB 5.1","note":"TTL-индекс должен быть однопольным (составной TTL не поддерживается). Добавить expireAfterSeconds к существующему однопольному не-TTL индексу через collMod можно в 5.1+; изменение срока существующего TTL работает и в более старых версиях. Чтобы удалять в определённое время, используйте expireAfterSeconds: 0 и храните время удаления в поле.","tags":["ttl","expireAfterSeconds","collMod","auto delete","index","metrics.ttl"]},{"id":"wt-cache-pressure","task":"Измерение давления на кэш WiredTiger (заполнение, доля грязных данных, вытеснение потоками приложения)","category":"Производительность","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":"Показывает размер кэша WiredTiger, процент заполнения и грязных данных, сколько страниц были вынуждены вытеснить потоки приложения, и состояние тикетов чтения/записи.","when":"Когда растут чтения с диска, внезапно скачет задержка или падает пропускная способность записи; при определении размера кэша.","caution":"Когда заполнение превышает 80% (eviction_target), начинается фоновое вытеснение; при 95% (eviction_trigger) в вытеснении участвуют и потоки приложения, и запросы замедляются. Для грязных данных 5% — цель, 20% — триггер; постоянное значение около 20% означает нехватку производительности записи на диск. Растущий счётчик pages evicted by application threads — это задержка, которую пользователи ощущают напрямую. Значения — накопительные счётчики; смотрите на разницу между двумя замерами.","since":"MongoDB 7.0","note":"Размер кэша по умолчанию — большее из (RAM - 1 GB) / 2 и 256 MB; в контейнерах вычисляется по лимиту cgroup. Увеличение storage.wiredTiger.engineConfig.cacheSizeGB уменьшает страничный кэш ОС; выход за половину RAM редко помогает. В 7.0 число тикетов регулируется динамически; в 8.0+ метрики тикетов появляются также в serverStatus().queues.execution.","tags":["wiredtiger","cache","eviction","dirty","tickets","memory"]},{"id":"dump-restore-archive","task":"Архивный бэкап через mongodump/mongorestore и восстановление одной коллекции","category":"Бэкап","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":"Создаёт резервную копию базы в одном архивном файле gzip; восстанавливает из архива всю базу или одну коллекцию, при необходимости под другим именем (nsFrom/nsTo).","when":"Для логических бэкапов, переноса данных между средами или восстановления одной коллекции, ошибочно удалённой или повреждённой.","caution":"mongodump — логический бэкап: на больших объёмах он медленный, а индексы при восстановлении перестраиваются. Дамп без --oplog на работающей системе не согласован на момент времени. --drop удаляет целевую коллекцию перед началом восстановления; сначала восстановите под другим именем через nsTo и проверьте. Восстановление пишет на primary и может вызвать отставание репликации.","since":"MongoDB 4.4","note":"Начиная с 4.4 инструменты (mongodump/mongorestore) версионируются отдельно от сервера (100.x). Добавьте readPreference=secondary в URI, чтобы снять нагрузку чтения с primary во время бэкапа. Чтобы перенести также пользователей и роли, используйте полный дамп экземпляра с --restoreDbUsersAndRoles или отдельно сохраните базу admin.","tags":["mongodump","mongorestore","backup","archive","nsFrom","restore"]},{"id":"pitr-oplog-replay","task":"Восстановление на момент времени с воспроизведением oplog (возврат к состоянию до ошибочного drop/delete)","category":"Бэкап","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":"Делает полный бэкап согласованным с помощью --oplog, затем воспроизводит oplog, накопленный после бэкапа, до метки времени ошибочной операции (drop, массовое удаление), возвращая данные к этому моменту.","when":"Когда коллекция была удалена по ошибке или выполнено ошибочное массовое update/delete и нужно последнее состояние до повреждения.","caution":"Восстанавливайте в отдельную цель, а не в рабочую систему, и переносите нужные данные после проверки. oplog от начала бэкапа до ошибочной операции должен всё ещё находиться в окне oplog; операции, выпавшие из окна, восстановить нельзя. --oplog работает только с полным дампом экземпляра (его нельзя сочетать с --db). Согласованное PITR с mongodump на шардированном кластере невозможно; нужен снимок файловой системы или продукт резервного копирования.","since":"MongoDB 4.4","note":"--oplogLimit=<seconds>:<ordinal> не применяет записи с указанной меткой времени и позже; если передать ts ошибочной операции, она будет исключена. Значения ts выглядят в oplog как Timestamp(t, i). Снимайте дамп oplog сразу после ошибочной операции, пока окно не закрылось.","tags":["pitr","point in time","oplogReplay","oplogLimit","rollback","mongorestore"],"untested":true},{"id":"user-role-admin","task":"Определение пользовательской роли, создание пользователя, выдача/отзыв ролей и смена пароля","category":"Безопасность","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":"Определяет пользовательскую роль с конкретными действиями на уровне базы и кластера, создаёт пользователя с этой ролью и встроенными ролями, выдаёт и отзывает роли, меняет пароль и выводит пользователей во всех базах.","when":"При создании учётных записей приложений и мониторинга по принципу минимальных привилегий, при ревизии доступа или ротации паролей.","caution":"База, в которой определён пользователь (здесь admin), используется как authSource; она должна совпадать со строкой подключения приложения. Не выдавайте учётным записям приложений широкие роли, такие как root/dbOwner. Изменения ролей сразу применяются к существующим сессиям; смена пароля не разрывает открытые соединения и действует для новых.","since":"MongoDB 4.4","note":"passwordPrompt() запрашивает пароль, не оставляя его в истории оболочки или списке процессов. В наборах реплик и шардированных кластерах пользователи управляются через primary/mongos и реплицируются. Чтобы увидеть эффективные привилегии пользователя, используйте showPrivileges: true в getUser.","tags":["createUser","createRole","grantRolesToUser","privileges","rbac","password"]},{"id":"fcv-upgrade","task":"Проверка featureCompatibilityVersion (fCV) и последовательность мажорного обновления","category":"Обновление","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":"Показывает текущий fCV; задаёт порядок выполнения обновления бинарников и обновления fCV, которые являются отдельными шагами.","when":"При планировании мажорного обновления (например, 6.0 -> 7.0), когда после обновления недоступны новые возможности, или перед переходом на следующую мажорную версию.","caution":"Мажорные версии нельзя пропускать: для 5.0 -> 7.0 сначала перейдите на 6.0 и установите fCV 6.0. После повышения fCV файлы данных могут перейти на новый формат, и бинарный откат усложняется; повышайте fCV только после того, как новая версия несколько дней проработала без проблем. Бинарный откат в Community 7.0+ официально не поддерживается; стройте план отката на бэкапе. Перед обновлением проверьте совместимость драйверов.","since":"MongoDB 7.0","note":"Начиная с 7.0 setFeatureCompatibilityVersion требует confirm: true. Если переход fCV не удался, в выводе getParameter появляется targetVersion/previousVersion; повторите ту же команду для завершения. Значение fCV реплицируется внутри набора реплик.","tags":["fcv","featureCompatibilityVersion","upgrade","major upgrade","major","downgrade"]}]},"recipes/redis.json":{"engineId":"redis","engine":"Redis","order":6,"recipes":[{"id":"bgrewriteaof","task":"Перезапись (уплотнение) файла AOF","category":"Персистентность","command":"BGREWRITEAOF","what":"Переписывает файл AOF в фоне в более компактном виде.","when":"Когда файл AOF раздулся, чтобы сократить время перезапуска.","caution":"Во время fork растёт использование памяти и IO; осторожно в часы пик.","tags":["aof","rewrite","persistence","persistence"]},{"id":"bgsave","task":"Снимок RDB в фоне","category":"Персистентность","command":"BGSAVE","what":"Записывает снимок RDB на диск в фоне с помощью дочернего процесса (fork).","when":"Когда нужно сделать бэкап или создать снимок.","caution":"В момент fork использование памяти может временно вырасти из-за copy-on-write.","tags":["rdb","bgsave","snapshot","persistence","backup"]},{"id":"save","task":"Синхронный снимок RDB","category":"Персистентность","command":"SAVE","what":"Записывает снимок RDB на диск синхронно в основном потоке.","when":"Только перед обслуживанием/остановкой, когда клиенты не обслуживаются.","caution":"SAVE блокирует весь сервер, и до его завершения команды не обрабатываются; на продакшене предпочитайте BGSAVE.","tags":["rdb","save","snapshot","blocking","persistence"]},{"id":"lastsave","task":"Время последнего успешного снимка","category":"Персистентность","command":"LASTSAVE","what":"Возвращает Unix-метку времени последнего успешного сохранения RDB.","when":"Чтобы проверить, завершился ли BGSAVE.","caution":"Команда только для чтения; без риска.","tags":["lastsave","rdb","timestamp","check","persistence"]},{"id":"config-rewrite","task":"Запись текущей конфигурации на диск","category":"Персистентность","command":"CONFIG REWRITE","what":"Сохраняет в файл redis.conf изменения, сделанные во время работы через CONFIG SET.","when":"Когда нужно, чтобы изменения во время работы пережили перезапуск.","caution":"Перезаписывает существующий файл redis.conf; файл должен быть задан и доступен на запись.","tags":["config","rewrite","persistent","conf","setting"]},{"id":"config-appendonly","task":"Включение или отключение AOF","category":"Персистентность","command":"CONFIG SET appendonly yes","what":"Включает (yes) или отключает (no) персистентность AOF во время работы.","when":"Когда нужно включить или выключить персистентность AOF без перезапуска.","caution":"Отключение AOF ослабляет гарантию сохранности; при включении начальная перезапись AOF создаёт нагрузку IO.","tags":["aof","appendonly","config","persistence","setting"]},{"id":"config-save-rdb","task":"Правила снимков RDB","category":"Персистентность","command":"CONFIG SET save \"900 1 300 10 60 10000\"","what":"Задаёт правила (секунды/число изменений), запускающие автоматические снимки RDB.","when":"Когда нужно изменить частоту автоматических снимков.","caution":"Если передать пустое значение (\"\"), автоматическое сохранение RDB полностью отключается, и персистентность может быть потеряна.","tags":["rdb","save","config","snapshot","setting"]},{"id":"replicaof","task":"Перевод узла в реплику","category":"Репликация","command":"REPLICAOF redis-master 6379","what":"Делает этот узел репликой мастера с указанным хостом и портом и запускает синхронизацию.","when":"При добавлении новой реплики для чтения или назначении ролей.","caution":"Существующие данные на узле, становящемся репликой, отбрасываются, и с мастера забирается полная синхронизация.","since":"Redis 5.0","note":"Команда REPLICAOF заменила SLAVEOF в Redis 5.0; старая команда SLAVEOF по-прежнему работает как псевдоним.","tags":["replicaof","replica","replication","master","sync"]},{"id":"replicaof-no-one","task":"Повышение реплики до мастера","category":"Репликация","command":"REPLICAOF NO ONE","what":"Разрывает связь реплики узла, превращая его в самостоятельный мастер.","when":"Когда нужно повысить реплику в сценарии отработки отказа.","caution":"После повышения аккуратно управляйте топологией, чтобы не возникло расхождения данных (split-brain) со старым мастером.","since":"Redis 5.0","note":"REPLICAOF NO ONE заменила SLAVEOF NO ONE в Redis 5.0; старая SLAVEOF NO ONE по-прежнему работает как псевдоним.","tags":["replicaof","promote","master","failover","replication"]},{"id":"cluster-failover","task":"Ручная отработка отказа","category":"Кластер","command":"CLUSTER FAILOVER","what":"При запуске на реплике начинает контролируемую отработку отказа, повышая эту реплику до мастера.","when":"Когда нужно передать роль мастера реплике перед обслуживанием.","caution":"Эту команду нужно выполнять на целевой реплике, а не на самом мастере.","tags":["cluster","failover","promote","maintenance","replication"]},{"id":"cluster-meet","task":"Добавление узла в кластер","category":"Кластер","command":"CLUSTER MEET 10.0.0.12 6379","what":"Присоединяет узел с указанным IP и портом к gossip-сети кластера.","when":"При добавлении нового узла в кластер (масштабирование).","caution":"Если после добавления узла не выполнено назначение слотов (ADDSLOTS) или reshard, он не получает данных.","tags":["cluster","meet","node","add","node"]},{"id":"cluster-addslots","task":"Назначение слотов кластера","category":"Кластер","command":"CLUSTER ADDSLOTS 0 1 2 3 4 5","what":"Назначает указанные хеш-слоты этому мастер-узлу.","when":"При ручной настройке нового мастер-узла или исправлении распределения слотов.","caution":"Один слот нельзя назначить более чем одному узлу; если не все 16384 слота назначены, кластер переходит в состояние down.","tags":["cluster","addslots","slot","assign","node"]},{"id":"info-replication","task":"Состояние репликации","category":"Кластер","command":"INFO replication","what":"Показывает роль (master/replica), подключённые реплики и сведения о смещении репликации.","when":"При проверке отставания репликации и состояния связи.","caution":"Команда только для чтения; без риска.","tags":["info","replication","status","replication","lag"]},{"id":"cluster-info","task":"Состояние кластера","category":"Кластер","command":"CLUSTER INFO","what":"Показывает общее состояние кластера (ok/fail), число назначенных слотов и число известных узлов.","when":"При проверке здоровья кластера и целостности слотов.","caution":"Команда только для чтения; без риска.","tags":["cluster","info","status","slot","health"]},{"id":"config-maxmemory","task":"Установка maxmemory и политики","category":"Память","command":"CONFIG SET maxmemory 4gb maxmemory-policy allkeys-lru","what":"Задаёт лимит памяти и политику вытеснения, применяемую при превышении лимита. Redis 7.0+ принимает несколько параметров в одном CONFIG SET; в старых версиях выполните две отдельные команды CONFIG SET.","when":"Когда нужно ограничить использование памяти и определить поведение вытеснения.","caution":"При политике noeviction запись завершается ошибкой по достижении лимита; неверный лимит может привести к потере данных/простою.","tags":["maxmemory","policy","memory","eviction","config"]},{"id":"memory-doctor","task":"Анализ памяти","category":"Память","command":"MEMORY DOCTOR","what":"Анализирует использование памяти и формирует понятный отчёт о возможных проблемах.","when":"Для диагностики при подозрении на высокое использование памяти или фрагментацию.","caution":"Анализ только для чтения; для подробностей можно также использовать команду MEMORY STATS.","tags":["memory","doctor","memory","analysis","diagnostics"]},{"id":"slowlog-get","task":"Журнал медленных команд","category":"Обслуживание","command":"SLOWLOG GET 10","what":"Возвращает 10 последних записей о медленных командах (длительность, время, аргументы) из slowlog.","when":"При выявлении медленных команд и исследовании узких мест производительности.","caution":"Команда только для чтения; для очистки записей используйте SLOWLOG RESET.","tags":["slowlog","get","slow","diagnostics","performance"]},{"id":"slowlog-config","task":"Порог медленных команд","category":"Обслуживание","command":"CONFIG SET slowlog-log-slower-than 10000","what":"Задаёт порог длительности в микросекундах, при превышении которого команда записывается в slowlog (10000 = 10 ms).","when":"Когда нужно настроить чувствительность slowlog.","caution":"Значение 0 записывает каждую команду и добавляет накладные расходы; -1 отключает slowlog.","tags":["slowlog","threshold","threshold","config","slow"]},{"id":"slowlog-reset","task":"Сброс журнала медленных команд","category":"Обслуживание","command":"SLOWLOG RESET","what":"Очищает все записи в slowlog.","when":"При очистке записей для начала нового окна наблюдения.","caution":"Существующая история медленных команд удаляется безвозвратно.","tags":["slowlog","reset","reset","clear","maintenance"]},{"id":"client-kill","task":"Завершение клиентского соединения","category":"Обслуживание","command":"CLIENT KILL ADDR 10.0.0.5:53210","what":"Завершает клиентское соединение с указанным адресом.","when":"Когда нужно завершить зависшего или некорректно работающего клиента.","caution":"Соединение разрывается немедленно; выполняющиеся операции могут остаться незавершёнными. Убедитесь, что выбрали правильный адрес.","tags":["client","kill","client","kill","maintenance"]},{"id":"client-list","task":"Список подключённых клиентов","category":"Обслуживание","command":"CLIENT LIST","what":"Выводит всех подключённых клиентов с адресом, временем простоя, последней командой и другими сведениями.","when":"При проверке соединений или поиске проблемного клиента.","caution":"Команда только для чтения; без риска.","tags":["client","list","client","list","connection"]},{"id":"config-get","task":"Значение параметра","category":"Время выполнения","command":"CONFIG GET maxmemory-policy","what":"Возвращает текущее значение одного или нескольких параметров конфигурации (поддерживаются glob-шаблоны).","when":"При проверке рабочего значения настройки.","caution":"Команда только для чтения; без риска.","tags":["config","get","parameter","view","runtime"]},{"id":"config-set","task":"Изменение значения параметра","category":"Время выполнения","command":"CONFIG SET appendfsync everysec","what":"Меняет параметр конфигурации во время работы без перезапуска.","when":"Когда нужно немедленно изменить поведение сервера.","caution":"Изменение временное; для сохранения выполните CONFIG REWRITE. Неверное значение может снизить производительность/надёжность.","tags":["config","set","parameter","runtime","setting"]}]},"recipes/redis-ext.json":{"engineId":"redis","engine":"Redis","order":2,"recipes":[{"id":"mem-fragmentation","task":"Анализ использования памяти и фрагментации, включение active defrag","category":"Память","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":"Показывает разницу между памятью, выделенной Redis под данные (used_memory), и RSS, видимым ОС, коэффициент фрагментации и разбивку памяти; при необходимости включает active defrag во время работы.","when":"Когда RSS явно больше набора данных, память не уменьшается после массовых удалений/истечения срока или появляется риск OOM.","caution":"mem_fragmentation_ratio обманчив на маленьких наборах данных (например, 4.0 при 2 MB данных); абсолютное значение смотрите в mem_fragmentation_bytes. Коэффициент ниже 1 означает своппинг, а не фрагментацию, и вызывает задержки. Active defrag расходует CPU; ограничьте его через active-defrag-cycle-max. Для постоянства нужен CONFIG REWRITE или redis.conf.","since":"Redis 4.0","note":"Active defrag работает только в сборках с jemalloc, поставляемым с Redis. allocator_frag_ratio показывает внутреннюю фрагментацию, которую может исправить defrag; allocator_rss_ratio — место, не возвращённое аллокатором ОС. Фрагментация растёт быстрее при нагрузках, часто обновляющих значения разного размера.","tags":["memory","ram","fragmentation","activedefrag","rss","info memory"]},{"id":"bigkeys-memkeys","task":"Поиск больших ключей (--bigkeys, --memkeys, MEMORY USAGE)","category":"Память","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":"Сканирует пространство ключей через SCAN и сообщает ключи с наибольшим числом элементов и наибольшим объёмом памяти по каждому типу; показывает реальное потребление памяти и кодировку подозрительного ключа.","when":"Когда память неожиданно растёт, одна команда (HGETALL, SMEMBERS, DEL) вызывает задержку или один узел кластера использует больше памяти, чем остальные.","caution":"Сканирование использует SCAN и не блокирует сервер, но проходит всё пространство ключей; -i 0.01 делает паузу каждые 100 вызовов SCAN для снижения нагрузки. Удаление большого ключа через DEL блокирует основной поток; используйте UNLINK. MEMORY USAGE с SAMPLES 0 имеет сложность O(N) на больших коллекциях. Запуск на реплике не нагружает мастер.","since":"Redis 6.0","note":"--bigkeys смотрит на число элементов, --memkeys — на MEMORY USAGE; для типов вроде list/hash они могут выделить разные ключи. Маленькие hash/zset/list используют кодировку listpack и занимают гораздо меньше памяти; после превышения порогов, таких как hash-max-listpack-entries, они преобразуются в hashtable. В кластере запускайте на каждом мастере отдельно.","tags":["bigkeys","memkeys","big key","memory usage","keystats","large key"]},{"id":"hotkeys-lfu","task":"Поиск горячих ключей (--hotkeys, OBJECT FREQ)","category":"Производительность","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":"Выводит самые часто используемые ключи по счётчикам LFU; показывает счётчик частоты обращений отдельного ключа.","when":"Когда CPU одного узла приближается к 100%, слот/узел кластера получает гораздо больше трафика, чем остальные, или пропускная способность сети забита одним ключом.","caution":"--hotkeys работает только при maxmemory-policy *-lfu. Смена политики меняет и поведение вытеснения: если задан maxmemory и прежняя политика была volatile-* или noeviction, allkeys-lfu может вытеснять и ключи без TTL. Проводите анализ быстро и верните политику. Счётчики LFU логарифмические и со временем затухают; это не абсолютное число обращений.","since":"Redis 4.0","note":"После смены политики нужен некоторый трафик, прежде чем счётчики LFU станут осмысленными. Если политику изменить нельзя, альтернатива — короткая выборка MONITOR, но MONITOR сильно снижает пропускную способность на нагруженных системах. Для горячих ключей чтения долговременное решение — кэширование на стороне клиента (CLIENT TRACKING) или разделение ключа.","tags":["hotkeys","hot keys","lfu","object freq","hot key","cpu"]},{"id":"scan-unlink-pattern","task":"Поиск ключей по шаблону без KEYS и их безопасное удаление (SCAN + UNLINK)","category":"Обслуживание","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":"Сканирует пространство ключей курсором без блокировки сервера, подсчитывает ключи, соответствующие шаблону и типу, и массово удаляет их через UNLINK, освобождая память в фоне.","when":"При чистке ключей, оставленных старой версией приложения, чтобы узнать, сколько ключей под префиксом, или когда иначе понадобился бы KEYS на продакшене.","caution":"KEYS возвращает всё пространство ключей сразу и на больших данных блокирует сервер на секунды; не используйте его на продакшене. SCAN не даёт полной гарантии для ключей, добавленных/удалённых во время сканирования, и может вернуть один ключ несколько раз. Имена ключей с пробелами или кавычками ломаются в xargs. Перед удалением проверьте количество и несколько примеров; отмены нет.","since":"Redis 4.0","note":"Опция TYPE команды SCAN появилась в 6.0. COUNT — подсказка и не гарантирует число возвращённых элементов. В кластере --scan сканирует только подключённый узел; запускайте на каждом мастере. UNLINK освобождает большие значения в отдельном потоке через lazyfree; для маленьких ключей стоит столько же, сколько DEL.","tags":["scan","unlink","keys","bulk delete","pattern","match"]},{"id":"latency-diagnosis","task":"Диагностика источника задержек (LATENCY DOCTOR, гистограмма, commandstats)","category":"Производительность","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":"Монитор задержек фиксирует всплески задержки от событий fork, AOF fsync, цикла expire и медленных команд; показывает перцентили задержки по командам и самые дорогие команды по суммарному времени CPU; измеряет базовую задержку на уровне хоста.","when":"Когда приложение видит всплески p99-задержки, а жалобы на медленную работу продолжаются, хотя SLOWLOG чист.","caution":"latency-monitor-threshold по умолчанию 0 (выключен); после диагностики верните старое значение. SLOWLOG измеряет только время выполнения команды и не видит ожиданий сети и очереди; события LATENCY фиксируют также подвисания внутри сервера, такие как fork и fsync. --intrinsic-latency расходует CPU и должен запускаться на машине, где работает сервер Redis.","since":"Redis 7.0","note":"LATENCY HISTOGRAM и INFO latencystats появились в 7.0 (latency-tracking включён по умолчанию). Если в LATENCY HISTORY преобладают события fork, смотрите на размер набора данных и THP (transparent huge pages); если высоки события aof-fsync-always/aof-write, смотрите на задержку диска. Команды с высоким usec_per_call в commandstats указывают на использование O(N).","tags":["latency","slowness","latency doctor","histogram","commandstats","intrinsic-latency"]},{"id":"eviction-oom-diag","task":"Диагностика вытеснения, истечения срока и ошибок OOM","category":"Память","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":"Показывает число вытесненных и истёкших ключей, коэффициент попаданий в кэш, долю ключей с TTL и значение памяти, запускающее вытеснение, относительно maxmemory.","when":"Когда приложение получает 'OOM command not allowed when used memory > maxmemory', падает коэффициент попаданий в кэш или сообщают о неожиданном исчезновении ключей.","caution":"При политике noeviction команды записи получают ошибку OOM по достижении maxmemory, а чтение продолжает работать. При политиках volatile-* ключи без TTL никогда не вытесняются; если ключей с TTL нет, поведение как у noeviction (проверьте expires в INFO keyspace). Не задавайте maxmemory слишком близко к физической RAM: оставьте место для буферов репликации и copy-on-write во время fork.","since":"Redis 6.0","note":"Коэффициент попаданий = keyspace_hits / (keyspace_hits + keyspace_misses). Если evicted_keys стабильно растёт, а коэффициент попаданий падает, памяти недостаточно. Высокий expired_stale_perc означает большую долю истёкших, но ещё не удалённых ключей; цикл expire можно ускорить через active-expire-effort. Буферы реплик и буфер AOF входят в mem_not_counted_for_evict.","tags":["eviction","oom","maxmemory","expire","hit ratio","evicted_keys"]},{"id":"client-memory","task":"Поиск соединений, раздувающих память (output buffer, query buffer, maxmemory-clients)","category":"Память","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":"Сортирует соединения по общей памяти (tot-mem), выходному буферу (omem) и буферу запросов (qbuf); показывает pub/sub-соединения и лимиты буферов; задаёт общий верхний предел памяти клиентов.","when":"Когда used_memory растёт без роста данных, mem_clients_normal выглядит высоким или медленно читающий клиент pub/sub/MONITOR заполняет память.","caution":"Большой omem обычно указывает на клиента, медленно читающего результаты (большой ответ KEYS/HGETALL, медленный подписчик pub/sub, оставленный открытым MONITOR). При превышении maxmemory-clients клиенты, занимающие больше всего памяти, закрываются без предупреждения; связи реплик и мастера исключены, но соединения приложений могут оборваться. Для критичных соединений используйте CLIENT NO-EVICT on.","since":"Redis 7.0","note":"maxmemory-clients появился в 7.0, по умолчанию 0 (без ограничения). client-output-buffer-limit задаёт жёсткие/мягкие лимиты для классов pubsub и replica; для обычных клиентов по умолчанию ограничения нет. Закройте проблемное соединение через CLIENT KILL ID <id> (см. client-kill).","tags":["client list","omem","output buffer","maxmemory-clients","connections","pubsub"]},{"id":"persistence-health","task":"Диагностика ошибок RDB/AOF, времени fork и причины MISCONF","category":"Персистентность","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":"Показывает, успешны ли последний снимок RDB и перезапись/запись AOF, сколько ключей изменилось с последнего сохранения, число отложенных fsync и длительность fork.","when":"Когда приложение получает 'MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk', BGSAVE/BGREWRITEAOF завершается ошибкой или наблюдаются задержки, связанные с fork.","caution":"При stop-writes-on-bgsave-error yes (по умолчанию) после неудачного BGSAVE все записи отклоняются. Если заглушить ошибку значением 'no', риск потери данных будет скрыт; сначала устраните причину: диск заполнен, нет прав на запись в dir или fork не может получить память (vm.overcommit_memory=0). Время fork на большом наборе данных может достигать сотен миллисекунд, и в это время Redis не отвечает.","since":"Redis 7.0","note":"vm.overcommit_memory=1 и отключённые transparent huge pages в Linux — официальные рекомендации Redis. Растущий aof_delayed_fsync означает, что диск не успевает за fsync (appendfsync everysec). Подробности ошибок — в журнале Redis ('Background saving error', 'Can't save in background: fork').","tags":["misconf","bgsave","aof","fork","persistence","durability"]},{"id":"aof-repair","task":"Восстановление Redis, не запускающегося из-за повреждённого AOF (redis-check-aof, redis-check-rdb)","category":"Персистентность","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":"Проверяет файлы AOF (base + incr + manifest) и файл RDB; обрезает незавершённый или повреждённый хвост AOF, чтобы Redis снова мог запуститься.","when":"Когда Redis не запускается с 'Bad file format reading the append only file' или 'Unexpected EOF' после отключения питания, заполнения диска или внезапной остановки.","caution":"--fix удаляет всё от первой повреждённой точки до конца файла; если повреждение в середине файла, все последующие записи теряются. Перед подтверждением проверьте в выводе, сколько байт будет обрезано (ok_up_to, diff). Незавершённая последняя команда (например, большой HSET) отбрасывается целиком. Всегда сначала делайте копию.","since":"Redis 7.0","note":"При aof-load-truncated yes (по умолчанию) AOF, обрезанный только в конце, автоматически подрезается при запуске; redis-check-aof обычно нужен, когда повреждение в середине файла. В многочастном AOF 7.0+ файлы находятся в каталоге appenddirname, а базовый файл может быть в формате RDB. Для повреждённого RDB redis-check-rdb только диагностирует и не исправляет.","tags":["aof","redis-check-aof","corrupted","corrupt","repair","redis-check-rdb"]},{"id":"replication-backlog","task":"Устранение обрывов репликации и бесконечного цикла полной ресинхронизации (backlog, буфер реплики)","category":"Репликация","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":"Показывает состояние связи, разницу смещений и счётчики частичной/полной синхронизации на мастере и реплике; увеличивает replication backlog, чтобы короткие разрывы не превращались в полную синхронизацию, и лимит выходного буфера реплики, чтобы полная синхронизация не обрывалась на полпути.","when":"Когда реплика постоянно зацикливается на 'MASTER <-> REPLICA sync started', счётчик sync_full растёт или журнал мастера показывает 'scheduled to be closed ASAP for overcoming of output buffer limits'.","caution":"Каждая полная синхронизация — это fork + генерация RDB на мастере; цикл нагружает память и диск мастера. repl-backlog-size выделяется из памяти мастера. Увеличение лимита буфера реплики означает, что мастер может использовать столько же дополнительной памяти во время полной синхронизации; учтите это в расчёте maxmemory. Для постоянства изменений нужен CONFIG REWRITE или redis.conf.","since":"Redis 4.0","note":"Примерный размер backlog: объём записи мастера в секунду (прирост master_repl_offset) x допустимая длительность разрыва. Имя класса 'replica' в client-output-buffer-limit принимается вместо 'slave' начиная с 5.0; вывод CONFIG GET может по-прежнему показывать slave. Если диск медленный, repl-diskless-sync yes (по умолчанию в 7.0+) передаёт RDB через сокет без записи на диск.","tags":["replication","backlog","full sync","psync","replica","output buffer"]},{"id":"sentinel-status","task":"Проверка состояния Sentinel, текущего мастера и кворума","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":"Выводит мастеры, отслеживаемые Sentinel, адрес текущего мастера, известные реплики и другие sentinel; проверяет наличие кворума и большинства, необходимых для отработки отказа.","when":"Когда приложение подключается не к тому узлу, при проверке топологии после отработки отказа и перед каждым обслуживанием.","caution":"Каждый sentinel сообщает своё представление; GET-MASTER-ADDR-BY-NAME должен возвращать один и тот же адрес на всех sentinel, иначе есть сетевое разделение или расхождение конфигурации. s_down/o_down в поле flags или disconnected у реплики требуют срочного расследования. Порт Sentinel (26379) должен быть открыт в брандмауэре только между sentinel и клиентами.","since":"Redis 5.0","note":"SENTINEL REPLICAS заменила SENTINEL SLAVES в 5.0 (старое имя по-прежнему работает). Число sentinel должно быть нечётным и не меньше 3, с распределением по разным доменам отказа (хост/стойка/AZ). Если Redis использует ACL/пароли, в sentinel.conf нужно задать sentinel auth-user/auth-pass.","tags":["sentinel","master","quorum","ckquorum","topology","failover"],"untested":true},{"id":"sentinel-failover","task":"Ручная отработка отказа через Sentinel и очистка топологии","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":"Передаёт роль мастера наиболее подходящей реплике даже при здоровом мастере; затем проверяет новый мастер и удаляет из памяти sentinel уже не существующие реплики.","when":"Перед плановым обслуживанием хоста мастера, во время учений по отработке отказа или при переносе мастера в другой ЦОД.","caution":"Во время отработки отказа запись прерывается на несколько секунд, и данные, записанные на мастер, но ещё не реплицированные, могут быть потеряны. Реплики с replica-priority 0 никогда не выбираются; если подходящей реплики нет, команда возвращает ошибку. Если SENTINEL RESET выполнить на всех sentinel одновременно, сведения о топологии временно теряются; выполняйте на каждом по отдельности и с интервалом. Убедитесь, что клиенты используют драйвер, запрашивающий адрес мастера у Sentinel.","since":"Redis 5.0","note":"SENTINEL FAILOVER не запрашивает согласия других sentinel, но распространяет новую эпоху конфигурации. Порядок выбора: replica-priority (меньший первым), смещение репликации (большее первым), run ID. Когда старый мастер возвращается после отработки отказа, Sentinel автоматически перенастраивает его как реплику нового мастера.","tags":["sentinel","failover","manual failover","master","reset","switchover"],"untested":true},{"id":"cluster-check-fix","task":"Проверка здоровья кластера и исправление несогласованности слотов (--cluster check/fix)","category":"Кластер","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":"Проверяет, что все узлы видят одинаковую карту слотов, что все 16384 слота назначены и нет незавершённых (migrating/importing) слотов; исправляет найденные проблемы и показывает, на каком слоте/узле находится ключ.","when":"При появлении cluster_state:fail, когда клиенты получают CLUSTERDOWN или постоянные ошибки MOVED/ASK, и после прерванного reshard.","caution":"--cluster fix может назначить бесхозные слоты произвольным узлам и переносить ключи в открытых слотах; сначала изучите вывод check. Если потеряны мастер и все его реплики, данные в этих слотах пропали; fix лишь назначает слот пустым другому узлу. При cluster-require-full-coverage yes (по умолчанию) один бесхозный слот останавливает запись во всём кластере.","since":"Redis 7.0","note":"CLUSTER SHARDS появилась в 7.0 и заменяет CLUSTER SLOTS. Если включена аутентификация, добавляйте -a <password> или --user/--pass к командам --cluster. Флаги fail/pfail в выводе CLUSTER NODES означают, что другие узлы считают узел недоступным.","tags":["cluster","check","fix","slot","clusterdown","cluster nodes"],"untested":true},{"id":"cluster-reshard","task":"Добавление узлов, перенос слотов и удаление узлов в кластере (add-node, reshard, rebalance)","category":"Кластер","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":"Добавляет в кластер новый мастер или реплику, онлайн переносит слоты с ключами между узлами, автоматически перебалансирует распределение слотов и удаляет опустошённый узел из кластера.","when":"При горизонтальном масштабировании памяти/пропускной способности, распределении нагрузки горячего узла или выводе узла из эксплуатации.","caution":"Миграция слотов переносит ключи по одному через MIGRATE; при переносе больших ключей оба узла могут кратковременно блокироваться. Во время миграции клиенты получают перенаправления ASK; нужен клиент с поддержкой кластера. Если reshard прерван, слоты остаются в состоянии migrating/importing; завершите через --cluster fix. Не удаляйте через del-node мастер, у которого ещё есть слоты; сначала перенесите их.","since":"Redis 5.0","note":"ID узлов берутся из CLUSTER NODES или CLUSTER MYID. --cluster-pipeline задаёт, сколько ключей переносит каждый вызов MIGRATE (по умолчанию 10). --cluster rebalance с --cluster-weight позволяет задать узлам разные веса. Команды redis-cli --cluster заменили redis-trib.rb в 5.0.","tags":["cluster","reshard","rebalance","add-node","slot","del-node"],"untested":true},{"id":"acl-users","task":"Создание пользователей ACL, проверка разрешений и сохранение","category":"Безопасность","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":"Создаёт пользователей для приложений и мониторинга с правами по префиксу ключей, каналу pub/sub и категории команд; проверяет через DRYRUN, разрешена ли команда, не выполняя её, и записывает ACL на диск.","when":"При переходе от одного общего пароля (requirepass) к доступу по пользователям, изоляции приложений от ключей друг друга или создании учётной записи только для чтения для инструмента мониторинга.","caution":"Порядок правил важен: resetchannels удаляет все правила &channel, заданные до него. -@dangerous блокирует также команды KEYS, FLUSHALL, CONFIG и DEBUG; проверьте команды, используемые приложением, через DRYRUN. Если пользователь default остаётся включённым с nopass и +@all, ACL не дают защиты; выполните ACL SETUSER default off после создания администратора. В redis-cli, если AUTH с --user/--pass не удался, соединение продолжается от пользователя default, и команда всё равно выполняется; поэтому для проверки разрешений используйте DRYRUN.","since":"Redis 7.0","note":"ACL появились в 6.0; ACL DRYRUN, селекторы и правила подкоманд (client|list) добавлены в 7.0. aclfile и строки user в redis.conf нельзя использовать одновременно. Пароли хранятся в виде хешей SHA-256; ACL GETUSER не показывает пароль в открытом виде.","tags":["acl","setuser","users","permissions","dryrun","security"]},{"id":"acl-log-denied","task":"Расследование ошибок NOPERM и WRONGPASS (ACL LOG)","category":"Безопасность","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":"Выводит отклонённые обращения к командам, ключам и каналам и неудачные попытки AUTH с именем пользователя, объектом, числом повторов и сведениями о клиенте (IP, последняя команда); показывает накопительные счётчики отказов.","when":"Когда приложение получает NOPERM или WRONGPASS после деплоя, чтобы найти команду, сломанную ужесточённым правилом ACL, или для обнаружения подбора паролей.","caution":"ACL LOG хранится в памяти, очищается при перезапуске и ограничен acllog-max-len (по умолчанию 128) записями; при повторении той же ошибки увеличивается count, а не добавляется строка. Поле client-info содержит IP клиента и используемого пользователя; перед сбросом сохраните нужное.","since":"Redis 7.0","note":"Счётчики acl_access_denied_* добавлены в INFO stats в 7.2; подключите их к мониторингу для оповещения о росте. Поля entry-id и timestamp-created/last-updated появились в 7.2. Попытки с неверным паролем отображаются как reason=auth, отсутствие разрешения на команду — как reason=command.","tags":["acl log","noperm","wrongpass","access denied","auth","security"]},{"id":"keyspace-notifications","task":"Мониторинг событий удаления и истечения ключей через keyspace notifications","category":"Обслуживание","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":"Публикует события по ключам (expire, del, set и т. п.) в каналы pub/sub, чтобы в реальном времени видеть, какой ключ и когда был удалён или истёк.","when":"Чтобы доказать, какая команда или истечение срока удаляет ключи, когда они 'исчезают сами', или для процессов, зависящих от событий истечения (закрытие сессии).","caution":"События добавляют нагрузку на CPU и сеть; 'A' (все классы) особенно дорог на нагруженных системах, поэтому включайте только нужные классы и потом выключайте. Pub/sub работает по принципу fire-and-forget: неподключённый подписчик пропускает событие, ничего не сохраняется. Событие expired генерируется, когда ключ реально удаляется (при обращении или фоновым циклом expire), а не в момент истечения TTL.","since":"Redis 2.8","note":"Для ключей, удалённых вытеснением, добавьте класс e (evicted). В кластере события публикуются только на узле, хранящем ключ; подписывайтесь на каждый мастер отдельно. Для постоянной настройки задайте notify-keyspace-events в redis.conf.","tags":["keyspace notifications","notify-keyspace-events","expired","pubsub","events","psubscribe"]}]},"recipes/elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"recipes":[{"id":"flush","task":"Flush индекса","category":"Жизненный цикл индекса","command":"POST /customer/_flush","what":"Выполняет коммит Lucene: надёжно записывает данные индексации из памяти на диск в виде сегментов и обрезает записи translog, которые больше не нужны.","when":"Чтобы уменьшить translog перед обслуживанием или перезапуском.","caution":"Обычно выполняется автоматически; ручной flush вызывает небольшое увеличение IO.","tags":["flush","translog","index","customer","maintenance"]},{"id":"force-merge","task":"Слияние сегментов индекса (force merge)","category":"Жизненный цикл индекса","command":"POST /customer/_forcemerge?max_num_segments=1","what":"Сливает сегменты индекса, чтобы уменьшить их число и улучшить производительность чтения.","when":"Только на индексах, в которые больше не пишут (только чтение), для повышения производительности запросов.","caution":"Потребляет много IO и CPU; никогда не запускайте на индексах с активной записью. Может длиться долго и не может быть отменён.","tags":["forcemerge","segment","merge","merge segments","performance"]},{"id":"rollover","task":"Rollover через псевдоним","category":"Жизненный цикл индекса","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":"Для управления размером индексов с временными рядами (альтернатива/дополнение к ILM).","caution":"У псевдонима должен быть единственный индекс записи через is_write_index; неверная конфигурация блокирует rollover.","tags":["rollover","alias","index","time series","ilm"]},{"id":"set-refresh-interval","task":"Изменение интервала refresh","category":"Жизненный цикл индекса","command":"PUT /customer/_settings\n{\n  \"index\": { \"refresh_interval\": \"30s\" }\n}","what":"Задаёт, как часто индекс делает новые документы доступными для поиска (refresh).","when":"При увеличении интервала для ускорения индексации во время массовых загрузок.","caution":"-1 полностью отключает refresh; документы не становятся доступными для поиска. После загрузки верните разумное значение.","tags":["refresh","interval","settings","bulk","refresh interval"]},{"id":"set-replicas","task":"Изменение числа реплик","category":"Жизненный цикл индекса","command":"PUT /customer/_settings\n{\n  \"index\": { \"number_of_replicas\": 2 }\n}","what":"Динамически меняет число реплика-шардов индекса.","when":"Для увеличения/уменьшения избыточности или производительности чтения.","caution":"Добавление реплик создаёт дополнительное использование диска и сетевой трафик (recovery); снижение до 0 убирает избыточность.","tags":["replica","settings","number_of_replicas","redundancy","capacity"]},{"id":"open-close-index","task":"Открытие или закрытие индекса","category":"Жизненный цикл индекса","command":"POST /customer/_close\nPOST /customer/_open","what":"Закрывает индекс для чтения/записи (освобождая ресурсы) или снова открывает его.","when":"Для снижения потребления ресурсов редко используемыми индексами или изменения некоторых настроек.","caution":"К закрытому индексу нельзя выполнять запросы и писать в него; пока он закрыт, он недоступен, кроме как через снимки.","tags":["open","close","index","open index","close index"]},{"id":"node-join-note","task":"Добавление узла в кластер","category":"Кластер/Узел","command":"GET /_cat/nodes?v","what":"Достаточно запустить новый узел с тем же cluster.name и правильными discovery.seed_hosts в elasticsearch.yml; эта команда проверяет, что узел присоединился.","when":"Для проверки членства при добавлении нового узла в кластер.","caution":"В 7.0+ нет minimum_master_nodes; конфигурация голосования управляется автоматически. Добавляйте и удаляйте master-eligible узлы по одному и никогда не удаляйте половину или больше из них сразу; при окончательном удалении более одного master-eligible узла сначала используйте POST _cluster/voting_config_exclusions?node_names=....","tags":["node","join","cluster","cat","add node"]},{"id":"node-decommission","task":"Освобождение узла (вывод из эксплуатации)","category":"Кластер/Узел","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.exclude._name\": \"node-3\"\n  }\n}","what":"Безопасно освобождает указанный узел, перенося его шарды на другие узлы.","when":"Чтобы перенести данные узла перед выводом его из эксплуатации.","caution":"Перемещение шардов создаёт большую нагрузку на сеть/IO; убедитесь, что у оставшихся узлов достаточно диска и ресурсов. После удаления узла снимите настройку значением null. Настройки transient устарели начиная с 7.16; используйте persistent.","tags":["decommission","exclude","allocation","drain","node"]},{"id":"reroute-shard","task":"Ручной перенос шарда (reroute)","category":"Кластер/Узел","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":"Вручную переносит определённый шард с одного узла на другой.","when":"Для исправления несбалансированного распределения шардов или освобождения определённого узла.","caution":"Может конфликтовать с автоматической перебалансировкой; перенос больших шардов создаёт нагрузку на сеть и IO.","tags":["reroute","shard","move","relocate","cluster"]},{"id":"allocation-enable","task":"Включение или отключение распределения шардов","category":"Кластер/Узел","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"cluster.routing.allocation.enable\": \"primaries\"\n  }\n}","what":"Ограничивает распределение шардов по всему кластеру (primaries: распределяются только первичные, none: ничего) или возвращает значение по умолчанию (all / null).","when":"Перед поочерёдным перезапуском, чтобы предотвратить ненужные перемещения реплик (официально рекомендуемое значение — \"primaries\").","caution":"Не забудьте потом вернуть настройку значением null (или \"all\"); если ограничение останется, реплики останутся неназначенными. Настройки transient устарели; используйте persistent.","tags":["allocation","enable","restart","shard","cluster"]},{"id":"cluster-settings","task":"Изменение настройки кластера","category":"Кластер/Узел","command":"PUT /_cluster/settings\n{\n  \"persistent\": {\n    \"indices.recovery.max_bytes_per_sec\": \"100mb\"\n  }\n}","what":"Меняет постоянную (persistent) или временную (transient) настройку уровня кластера.","when":"При настройке поведения кластера, например скорости восстановления.","caution":"Настройки persistent сохраняются после перезапуска; настройки transient теряются и устарели начиная с 7.16, поэтому не используйте их. Чтобы сбросить настройку к значению по умолчанию, задайте ей null. Неверное значение влияет на стабильность кластера.","tags":["cluster","settings","persistent","transient","setting"]},{"id":"snapshot-repo","task":"Регистрация репозитория снимков","category":"Снимки","command":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": {\n    \"location\": \"/mnt/es_backups\"\n  }\n}","what":"Регистрирует репозиторий для хранения снимков.","when":"При первичной настройке инфраструктуры резервного копирования.","caution":"Для репозитория fs путь должен быть задан в path.repo и доступен на всех master- и data-узлах.","tags":["snapshot","repository","repo","backup","register"]},{"id":"snapshot-create","task":"Создание снимка","category":"Снимки","command":"PUT /_snapshot/backup_repo/snap1?wait_for_completion=false\n{\n  \"indices\": \"customer\",\n  \"include_global_state\": false\n}","what":"Создаёт в репозитории резервную копию указанных индексов на момент времени (снимок).","when":"Для регулярного резервного копирования или как страховочная копия перед крупным изменением.","caution":"wait_for_completion=true держит HTTP-соединение в ожидании долгое время на больших индексах; для больших снимков используйте false.","tags":["snapshot","create","backup","backup snapshot","take"]},{"id":"snapshot-restore","task":"Восстановление из снимка","category":"Снимки","command":"POST /_snapshot/backup_repo/snap1/_restore\n{\n  \"indices\": \"customer\",\n  \"rename_pattern\": \"customer\",\n  \"rename_replacement\": \"customer_restored\"\n}","what":"Восстанавливает индексы из снимка в кластер (при необходимости с переименованием).","when":"Для восстановления после потери данных или переноса резервной копии в тестовую среду.","caution":"Для восстановления под тем же именем целевой индекс должен быть закрыт или удалён; восстановить поверх открытого индекса нельзя.","tags":["snapshot","restore","restore snapshot","recovery","backup"]},{"id":"cache-clear","task":"Очистка кэша","category":"Обслуживание","command":"POST /customer/_cache/clear","what":"Очищает кэши запросов, request и fielddata индекса.","when":"Для снижения давления на память или устранения проблем с fielddata.","caution":"После очистки кэша первые запросы выполняются медленнее, пока кэш снова не прогреется.","tags":["cache","clear","clear cache","fielddata","maintenance"]},{"id":"pending-tasks","task":"Ожидающие задачи кластера","category":"Обслуживание","command":"GET /_cluster/pending_tasks","what":"Выводит задачи уровня кластера, ожидающие обработки на master-узле.","when":"Для диагностики нагрузки на master, когда кластер замедляется или обновления зависают.","caution":"Команда только для чтения; без риска.","tags":["pending","tasks","master","diagnostics","pending tasks"]},{"id":"hot-threads","task":"Анализ hot threads","category":"Обслуживание","command":"GET /_nodes/hot_threads","what":"Показывает трассировки стека потоков, потребляющих больше всего CPU на узлах.","when":"При диагностике источника высокой загрузки CPU.","caution":"Команда только для чтения; вывод может быть длинным, сужайте его параметром thread_type.","tags":["hotthreads","cpu","diagnostics","thread","performance"]},{"id":"cat-shards","task":"Распределение шардов","category":"Обслуживание","command":"GET /_cat/shards?v&s=state","what":"Выводит все шарды с индексом, типом (primary/replica), состоянием и узлом размещения.","when":"При проверке размещения шардов и неназначенных шардов.","caution":"Команда только для чтения; без риска.","tags":["cat","shards","distribution","state","shard"]},{"id":"allocation-explain","task":"Почему шард не назначен","category":"Обслуживание","command":"GET /_cluster/allocation/explain\n{\n  \"index\": \"customer\",\n  \"shard\": 0,\n  \"primary\": true\n}","what":"Подробно объясняет, почему определённый шард не удалось распределить (unassigned).","when":"При поиске причины неназначенных шардов в жёлтом/красном кластере.","caution":"Диагностическая команда только для чтения; без риска.","tags":["allocation","explain","unassigned","diagnostics","shard"]},{"id":"cluster-health","task":"Здоровье кластера","category":"Обслуживание","command":"GET /_cluster/health","what":"Показывает общее здоровье кластера (green/yellow/red) и число активных и неназначенных шардов.","when":"При быстрой проверке состояния кластера.","caution":"Команда только для чтения; без риска.","tags":["cluster","health","health check","status","green"]},{"id":"refresh-index","task":"Ручной refresh индекса","category":"Жизненный цикл индекса","command":"POST /customer/_refresh","what":"Делает ожидающие изменения в индексе сразу доступными для поиска.","when":"Когда нужно, чтобы данные можно было запрашивать сразу после массовой загрузки.","caution":"Частые ручные refresh могут порождать много мелких сегментов и снижать производительность.","tags":["refresh","index","search","segment","customer"]}]},"recipes/elasticsearch-ext.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":2,"recipes":[{"id":"red-yellow-triage","task":"Порядок разбора красного/жёлтого кластера (какой индекс, какой шард, почему)","category":"Кластер/Узел","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":"Задаёт упорядоченный порядок разбора: от состояния кластера к проблемным индексам, затем к неназначенным шардам и причине, по которой они не распределены (unassigned.reason, allocation explain); _health_report кратко показывает, какой индикатор (диск, доступность шардов, master) не в порядке.","when":"Как первое действие, когда кластер становится красным или жёлтым.","caution":"Red: хотя бы один первичный шард не назначен; запись в этот индекс и поиски, затрагивающие этот шард, завершаются ошибкой. Yellow: все первичные на месте, но реплик не хватает; данные доступны, но избыточности нет. В кластере из одного узла индексы с replicas > 0 постоянно остаются жёлтыми (решение same_shard). Если узел недавно ушёл, delayed_unassigned_shards намеренно удерживаются до истечения index.unassigned.node_left.delayed_timeout (по умолчанию 1m).","since":"Elasticsearch 8.7","note":"_health_report появился в 8.7; в более старых версиях используйте первые четыре шага. Значения unassigned.reason: NODE_LEFT (узел ушёл), ALLOCATION_FAILED (достигнут лимит повторов, см. retry-failed-allocation), INDEX_CREATED, CLUSTER_RECOVERED. Для конкретного шарда используйте рецепт allocation-explain с телом index/shard/primary.","tags":["red","yellow","cluster health","unassigned","health report","triage"]},{"id":"retry-failed-allocation","task":"Повторное распределение шардов, достигших лимита повторов (reroute retry_failed)","category":"Кластер/Узел","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":"Показывает подробности сбоев шардов, распределение которых остановлено после последовательных неудач (ALLOCATION_FAILED, max_retry), и снова запускает распределение; отслеживает ход восстановления.","when":"Когда allocation explain показывает 'shard has exceeded the maximum number of retries'; после устранения заполненного диска, повреждённого translog или временной сетевой проблемы.","caution":"Запуск retry_failed без устранения причины заставит шард сделать ещё 5 попыток и снова упасть; сначала устраните ошибку из unassigned_info.details. Если много шардов одновременно уходит в восстановление, растёт нагрузка на сеть и диск; она ограничивается cluster.routing.allocation.node_concurrent_recoveries и indices.recovery.max_bytes_per_sec.","since":"Elasticsearch 7.0","note":"Лимит повторов — index.allocation.max_retries (по умолчанию 5). Elasticsearch и сам периодически повторяет попытки, но retry_failed убирает ожидание. metric=none убирает из ответа большой cluster state.","tags":["reroute","retry_failed","allocation failed","max_retries","unassigned","recovery"]},{"id":"allocate-stale-primary","task":"Распределение потерянного первичного шарда как крайняя мера (allocate_stale_primary)","category":"Кластер/Узел","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":"Когда узел с актуальной первичной копией безвозвратно потерян, назначает первичной более старую (не in-sync) копию на другом узле, чтобы вывести индекс из красного состояния.","when":"Только когда узел с первичной копией восстановить невозможно, а восстановление из снимка недоступно или займёт больше времени.","caution":"Устаревшая копия не содержит последних записей; все записи, сделанные после этой копии, теряются безвозвратно. Если потерянный узел позже вернётся, более свежая копия на нём будет удалена. Сначала рассмотрите восстановление узла и восстановление из снимка. allocate_empty_primary создаёт шард полностью пустым и удаляет все данные этого шарда; используйте только когда не осталось ни одной копии.","since":"Elasticsearch 7.0","note":"В выводе _shard_stores тип распределения отображается как 'primary', 'replica' или 'unused'; не выбирайте копии со store_exception. Сначала можно выполнить команду с ?dry_run=true&explain=true, чтобы увидеть решения. После операции закройте разрыв в данных переиндексацией со стороны приложения.","tags":["allocate_stale_primary","red","primary","data loss","shard_stores","reroute"],"untested":true},{"id":"disk-watermark","task":"Управление дисковыми watermark и снятие блокировки записи flood-stage","category":"Кластер/Узел","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":"Показывает использование диска по узлам и действующие значения watermark; настраивает пороги low/high/flood_stage и снимает блокировку read_only_allow_delete, установленную на индексы на стадии flood.","when":"Когда запись отклоняется с 'TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block', новые шарды не распределяются или шарды начинают уходить с заполненного узла.","caution":"Выше low новые шарды на узел не распределяются; выше high шарды переносятся с узла; выше flood_stage индексы, у которых есть шард на этом узле, становятся только для чтения. Повышение порогов лишь выигрывает время; долговременное решение — удаление старых индексов, сокращение реплик или добавление дисков/узлов. Если диск заполнится полностью, шарды могут быть повреждены.","since":"Elasticsearch 8.5","note":"В 7.4+ блокировка снимается автоматически, когда использование диска опускается ниже high watermark; ручное снятие сокращает ожидание. В 8.5+ большие диски используют max_headroom (low 200GB, high 150GB, flood 100GB) как абсолютное свободное место вместо процента; эти запасы по умолчанию действуют, только пока watermark оставлены по умолчанию, поэтому явное задание процентов, как выше, их отключает. Watermark действуют и в кластерах с одним data-узлом.","tags":["disk watermark","flood_stage","read_only_allow_delete","disk full","allocation","cat allocation"]},{"id":"rolling-restart","task":"Поочерёдный перезапуск / поочерёдное обновление (узел за узлом, без лишних перемещений шардов)","category":"Кластер/Узел","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":"Задаёт порядок перезапуска или обновления узлов по одному, не давая репликам заново копироваться на другие узлы, пока узел недоступен; когда узел возвращается, шарды быстро восстанавливаются из локальной копии.","when":"При последовательном перезапуске узлов для патча ОС, изменения JVM/heap, изменения elasticsearch.yml или минорного/мажорного обновления версии.","caution":"Не переходите к следующему узлу, не дождавшись green; при двух одновременно недоступных узлах могут пропасть все копии шарда. Master-eligible узлы перезапускайте последними и по одному; если большинство потеряно, кластер не сможет избрать master. Если забыть снова включить allocation.enable, реплики останутся неназначенными. При обновлении сначала обновляются data-узлы (сначала frozen/cold, hot в конце), затем master.","since":"Elasticsearch 7.16","note":"Если есть задания ML, выполните POST _ml/set_upgrade_mode?enabled=true в начале и enabled=false в конце. Synced flush удалён в 8.0; достаточно обычного _flush. Используйте persistent, так как настройки transient устарели начиная с 7.16. Для коротких перезапусков вместо отключения распределения можно также увеличить index.unassigned.node_left.delayed_timeout.","tags":["rolling restart","rolling upgrade","allocation","primaries","maintenance","upgrade"],"untested":true},{"id":"shard-sizing","task":"Аудит размеров и числа шардов (выявление oversharding / слишком больших шардов)","category":"Обслуживание","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":"Выводит самые большие шарды, первичные размеры индексов, число шардов на узел, общее число шардов и лимит cluster.max_shards_per_node.","when":"При планировании мощностей, когда растёт время восстановления или давление на heap, и при ошибке 'this action would add [x] shards, but this cluster currently has [y]/[z] maximum normal shards open'.","caution":"Много очень маленьких шардов (oversharding) нагружает master и heap; очень большие шарды растягивают восстановление и перебалансировку на часы. Число первичных шардов нельзя изменить после создания индекса; для исправления нужен shrink, split или reindex. Повышение max_shards_per_node снимает лимит, но проблему не решает.","since":"Elasticsearch 8.3","note":"Общая рекомендация Elastic: 10-50 GB на шард и не более примерно 200 миллионов документов. В 8.3+ старое правило '20 шардов на GB heap' заменено оценкой heap по числу отображённых полей. Для временных рядов фиксируйте размер шарда через max_primary_shard_size в rollover.","tags":["shard size","oversharding","cat shards","max_shards_per_node","capacity","sizing"]},{"id":"shrink-index","task":"Сжатие индекса со слишком большим числом шардов (shrink)","category":"Жизненный цикл индекса","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":"Уменьшает число первичных шардов индекса, в который больше не пишут, создавая жёсткие ссылки на его сегменты в новом индексе; снижает накладные расходы heap и числа шардов.","when":"Когда ежедневные индексы созданы с большим числом шардов, чем нужно, лимит числа шардов близок или перед переносом старых индексов на warm-уровень.","caution":"Во время shrink исходный индекс закрыт для записи; применяйте только к индексам, в которые больше не пишут. Целевое число шардов должно быть делителем исходного (8 -> 4, 2 или 1). Узлу, где собираются все копии, нужно столько же свободного диска, сколько занимает исходный индекс. При replicas 0 у исходного шарда одна копия; не удаляйте источник, пока операция не завершится и новый индекс не станет green.","since":"Elasticsearch 7.0","note":"После shrink удалите источник или переключите псевдоним на новый индекс. Операция обычно быстрая, так как на одном узле файлы в той же файловой системе связываются жёсткими ссылками; иначе файлы копируются. Для увеличения числа шардов используйте _split, а для автоматизации через ILM — действие shrink в фазе warm.","tags":["shrink","shard count","oversharding","index","blocks.write","warm"]},{"id":"ilm-policy-bootstrap","task":"Определение политики ILM, подключение через шаблон и запуск цепочки rollover","category":"Жизненный цикл индекса","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":"Создаёт политику ILM с фазами hot (rollover), warm (shrink + force merge) и delete, применяет политику и псевдоним rollover к новым индексам через шаблон и создаёт первый индекс цепочки с псевдонимом записи.","when":"При автоматизации размера, срока хранения и удаления индексов для временных рядов, таких как журналы или метрики.","caution":"min_age в фазах warm и delete отсчитывается от момента rollover, а не от создания индекса. Изменения политики применяются к существующим индексам только после завершения их текущей фазы. Shrink и forcemerge в фазе warm создают интенсивный IO; в индекс нельзя писать до завершения force merge. ILM по умолчанию срабатывает каждые 10 минут, поэтому условия проверяются не мгновенно.","since":"Elasticsearch 7.8","note":"Для новых проектов то же проще достигается с data stream: добавьте \"data_stream\": {} в шаблон индекса, и ни псевдоним rollover, ни начальный индекс не нужны. Условия rollover проверяются по обновлённой статистике. Составные шаблоны индексов (_index_template) появились в 7.8; устаревший API _template объявлен устаревшим.","tags":["ilm","lifecycle","rollover","policy","index template","retention"]},{"id":"ilm-explain-retry","task":"Диагностика застрявшего шага ILM и повтор (_ilm/explain, _ilm/retry)","category":"Жизненный цикл индекса","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":"Показывает индексы, ожидающие на шаге ERROR, в какой фазе/действии/шаге они застряли и причину сбоя (step_info); повторно запускает неудавшийся шаг после устранения причины.","when":"Когда rollover не происходит, старые индексы не удаляются или диск заполняется индексами, которые ILM должен был удалить.","caution":"_ilm/retry работает только для индексов на шаге ERROR; иначе возвращается 'has not encountered an error'. Частые причины: отсутствует настройка rollover_alias или у псевдонима is_write_index стоит на другом индексе, не хватает диска на одном узле для shrink, правило распределения (_tier_preference) невозможно выполнить. Удаление политики не снимает управление ILM с индексов; сначала удалите настройку политики с индексов.","since":"Elasticsearch 7.0","note":"Ошибки с is_auto_retryable_error true ILM повторяет сам (растёт failed_step_retry_count). Интервал проверки ILM задаётся indices.lifecycle.poll_interval (по умолчанию 10m). Чтобы вывести индекс из ILM, используйте POST <index>/_ilm/remove.","tags":["ilm","explain","retry","error step","rollover","lifecycle"]},{"id":"index-template-component","task":"Сборка шаблона индекса из шаблонов-компонентов и симуляция результата","category":"Жизненный цикл индекса","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":"Определяет повторно используемый шаблон-компонент маппинга, включает его в шаблон индекса и показывает итоговые настройки и маппинг, которые применились бы к заданному имени индекса, не создавая индекс.","when":"Когда новые индексы создаются с неверным маппингом или числом шардов (взрыв dynamic mapping, text вместо keyword), и перед выкаткой изменения шаблона.","caution":"Шаблон применяется только к вновь создаваемым индексам; маппинг существующих индексов не меняется (см. reindex-alias-swap). Если два шаблона соответствуют одному шаблону имён, используется только шаблон с наивысшим priority; они не объединяются. dynamic: strict отклоняет документы с неопределёнными полями; если приложение отправит новое поле, индексация завершится ошибкой.","since":"Elasticsearch 7.8","note":"Поле overlapping в выводе _simulate_index показывает другие шаблоны, соответствующие тому же шаблону имён и переопределяемые. Держите priority выше 200, чтобы не конфликтовать со встроенными шаблонами (logs-*-*, metrics-*-*). Составные шаблоны имеют приоритет над устаревшим API _template.","tags":["index template","component template","simulate","mapping","priority","dynamic strict"]},{"id":"reindex-alias-swap","task":"Изменение маппинга без простоя (reindex + атомарное переключение псевдонима)","category":"Жизненный цикл индекса","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":"Создаёт индекс с новым маппингом, копирует данные в фоне через reindex, сравнивает число документов и одной атомарной операцией переключает используемый приложением псевдоним на новый индекс.","when":"Когда нужно изменить тип существующего поля (text -> keyword, integer -> long), анализатор или заново определить число шардов; на существующем индексе это сделать нельзя.","caution":"Приложение должно подключаться к псевдониму, а не к имени индекса; иначе для переключения нужен деплой. Новые записи в источник во время reindex не попадают в цель: остановите запись или прямо перед переключением повторите reindex с op_type create + conflicts proceed, чтобы закрыть разрыв (обновления и удаления так не переносятся). Reindex создаёт нагрузку чтения на источник и записи на цель; ограничивайте через requests_per_second.","since":"Elasticsearch 7.0","note":"Документы, загруженные при refresh_interval -1, не попадают в подсчёт до refresh; перед сравнением выполните _refresh. Если _cat/count получает несколько индексов через запятую, он возвращает их сумму; для сравнения по индексам используйте _cat/indices. Скорость выполняющегося reindex можно изменить через POST _reindex/<task_id>/_rethrottle?requests_per_second=500. wait_for_completion=false пишет результат в индекс .tasks; проверьте response.failures через GET _tasks/<task_id>.","tags":["reindex","alias","mapping change","zero downtime","_aliases","no downtime"]},{"id":"tasks-cancel","task":"Поиск, отмена или замедление долгих задач поиска и reindex","category":"Обслуживание","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":"Сортирует выполняющиеся в кластере задачи по длительности, показывает поиски и bulk-операции с описанием запросов; отменяет выбранную задачу или все поиски и замедляет задания reindex/by-query.","when":"Когда заполняется пул потоков search, внезапно растёт CPU, дашборд нагружает кластер тяжёлой агрегацией или update_by_query влияет на продакшен.","caution":"Отмена кооперативна: задача останавливается в следующей контрольной точке; некоторые фазы (например, force merge) отменить нельзя. Отмена всех поисков возвращает пользователям ошибки. Отменённый reindex/by-query не откатывает уже сделанные изменения; цель содержит частичные данные. При отмене родительской задачи отменяются и дочерние.","since":"Elasticsearch 7.0","note":"Параметр detailed записывает тело запроса задач поиска в поле description. Для долговременной защиты используйте search.default_search_timeout, timeout в запросе поиска и search.max_buckets для больших агрегаций. В 7.x+ поиски, HTTP-соединение которых закрылось, отменяются автоматически.","tags":["tasks","cancel","kill query","rethrottle","long query","search"]},{"id":"search-slowlog","task":"Включение slow log поиска и индексации (пороги по индексу)","category":"Производительность","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":"Для заданного индекса записывает в slow log запросы, превысившие порог в фазах query и fetch и при индексации, вместе с телом запроса.","when":"Чтобы найти, какой запрос вызывает медленный поиск, или поймать тяжёлые запросы от определённого дашборда или клиента.","caution":"Порог измеряется на уровне шарда: на индексе с многими шардами каждый шард может записать свою строку. Очень низкий порог (0ms) существенно увеличивает объём журнала и IO; после диагностики выключите значением null. indexing.slowlog.source пишет в журнал исходный документ; если там чувствительные данные, установите false. Настройка действует только на заданный индекс; для новых индексов добавьте её в шаблон.","since":"Elasticsearch 8.0","note":"Файлы slow log пишутся в каталог logs как <cluster>_index_search_slowlog.json и <cluster>_index_indexing_slowlog.json; если log4j2 направлен в консоль, они идут в stdout. Настройка index.search.slowlog.level удалена в 8.0; уровень определяется порогами (warn/info/debug/trace). В 8.14+ index.search.slowlog.include.user: true записывает в журнал также пользователя, сделавшего запрос. Для подробного изучения одного запроса добавьте \"profile\": true в запрос поиска.","tags":["slowlog","slow log","slow search","search","indexing","threshold"]},{"id":"rejections-heap-breaker","task":"Диагностика отказов 429, давления на heap и circuit breaker","category":"Производительность","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":"Показывает запросы, отклонённые из-за заполненных очередей пулов потоков write/search, состояние heap/CPU по узлам, срабатывания circuit breaker, число old GC и использование памяти fielddata.","when":"Когда клиенты получают 429 Too Many Requests или es_rejected_execution_exception, появляется CircuitBreakingException ([parent] Data too large) или узлы страдают от долгих пауз GC.","caution":"Счётчики rejected накапливаются с момента запуска узла; смотрите на прирост между двумя замерами. Увеличение queue_size скрывает отказы, но увеличивает heap и задержки; настоящее решение — уменьшить размер bulk/конкурентность или добавить мощности. Если heap стабильно выше 85% и old GC выполняется часто, узел приближается к OOM. Включение fielddata на поле text быстро расходует heap.","since":"Elasticsearch 7.0","note":"Parent breaker по умолчанию — 95% heap (real memory circuit breaker, 7.0+). Отказы записи обычно сосредоточены на одном горячем узле: проверьте распределение шардов и маршрутизацию (_routing). Чтобы временно освободить fielddata, используйте POST _cache/clear?fielddata=true (см. cache-clear).","tags":["429","rejected","thread pool","circuit breaker","heap","fielddata"]},{"id":"slm-snapshot-monitor","task":"Автоматизация снимков и хранения через SLM, мониторинг хода снимка","category":"Снимки","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":"Определяет политику SLM, которая делает снимки по расписанию cron и удаляет старые по правилу хранения; показывает ход выполняющегося снимка по шардам, прошлые снимки и доступность репозитория.","when":"При настройке регулярного резервного копирования, при оповещении о сбое ночного снимка или для оценки времени завершения долгого снимка.","caution":"Расписание вычисляется в UTC. Если в один репозиторий пишет более одного кластера или репозиторий зарегистрирован как доступный на запись в другом кластере, он может быть повреждён; в других кластерах используйте readonly: true. Правило хранения удаляет только снимки, сделанные SLM. include_global_state true при восстановлении может восстановить также шаблоны и постоянные настройки кластера.","since":"Elasticsearch 7.5","note":"Снимки инкрементальны: копируются только сегменты, отсутствующие в предыдущем снимке, поэтому частые снимки не занимают пропорционального места в репозитории. SLM появился в 7.4, правило хранения — в 7.5. Без имени репозитория _status возвращает все выполняющиеся в кластере снимки; с именем прошлого снимка он читает из репозитория и может быть медленным.","tags":["slm","snapshot","retention","backup","schedule","snapshot status"],"untested":true},{"id":"security-user-role","task":"Создание роли и native-пользователя, смена пароля, выпуск API-ключа","category":"Безопасность","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":"Создаёт роль с доступом на чтение к шаблону индексов и пользователя native realm с этой ролью, меняет пароль, проверяет личность вызывающего и выпускает ограниченный по времени API-ключ с суженными привилегиями.","when":"При создании учётных записей для отчётов, приложений или мониторинга с минимальными привилегиями, при ротации паролей и при переводе сервисов с пользователей на API-ключи.","caution":"Не используйте суперпользователя elastic в приложениях. Привилегии API-ключа не могут превышать привилегии создавшего его пользователя; role_descriptors только сужают их. API-ключ ограничен снимком привилегий владельца на момент создания; последующие изменения ролей не влияют на существующие ключи. После смены пароля сервисы со старым паролем получают 401; спланируйте переключение. Значение encoded в ответе на создание API-ключа возвращается только один раз; сохраните его.","since":"Elasticsearch 8.0","note":"В 8.0+ безопасность включена по умолчанию, а пароль elastic генерируется при первом запуске. Инструмент elasticsearch-reset-password появился в 8.0. Выводите API-ключи через GET _security/api_key?name=rapor-key и отзывайте через DELETE _security/api_key. Сопоставление ролей для пользователей realm LDAP/AD/SAML выполняется через _security/role_mapping.","tags":["security","users","roles","api key","password","rbac"],"untested":true}]},"solver/slow-query.json":{"id":"slow-query","title":"Медленный запрос: углублённая диагностика","icon":"🐢","category":"Производительность","intro":"Медленный запрос или вся система. Сужайте поиск шаг за шагом: выполняйте проверку в каждом узле и выбирайте ветку по результату. В конце получаете первопричину + исправление.","start":"scope","nodes":{"scope":{"kind":"step","title":"Что медленно?","detail":"Медленный один или несколько конкретных запросов или вся система? Это разделяет расследование на одно из двух направлений: 'запрос' или 'ресурсы/узкое место'.","choices":[{"label":"Медленные один или несколько конкретных запросов","next":"running"},{"label":"Медленная вся система","next":"system"}]},"running":{"kind":"step","title":"Выполняется ли запрос прямо сейчас?","detail":"Это запрос, который выполняется/завис прямо сейчас, или 'в целом медленный'? Посмотрите на активные сессии.","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":"Выполняется / завис прямо сейчас","next":"blocked"},{"label":"В целом медленный; хочу посмотреть его план","next":"plan"}]},"blocked":{"kind":"step","title":"Ждёт ли запрос чего-то?","detail":"Если он активен, но не продвигается, скорее всего, он ждёт. Заполнены ли blocking_session / wait_event?","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":"Заблокирован другой сессией","next":"sol_blocking"},{"label":"Ждёт ресурс (IO / журнал / latch)","next":"wait"},{"label":"Ожидания нет; активно расходует CPU","next":"plan"}]},"wait":{"kind":"step","title":"Какое ожидание преобладает?","detail":"Преобладающее событие ожидания указывает тип узкого места.","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 (db file read / PAGEIOLATCH)","next":"sol_io"},{"label":"Ожидание журнала / commit (log file sync / WRITELOG)","next":"sol_log"},{"label":"Блокировка / enqueue (enq: TX / LCK)","next":"sol_blocking"}]},"plan":{"kind":"step","title":"Получите план выполнения (EXPLAIN)","detail":"Получите план. Есть ли полное сканирование таблицы, неожиданный порядок соединений или разрыв между оценочным и фактическим числом строк?","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":"Полное сканирование / нет индекса","next":"sol_index"},{"label":"Вчера был быстрым; план внезапно изменился","next":"sol_planchange"},{"label":"Обрабатывает слишком много строк / N+1","next":"sol_rewrite"},{"label":"План выглядит хорошо, но медленно","next":"stats"}]},"stats":{"kind":"step","title":"Актуальна ли статистика?","detail":"Большой разрыв между оценочным и фактическим числом строк (E-rows vs A-rows) — признак устаревшей статистики.","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":"Статистика устарела","next":"sol_stats"},{"label":"Статистика актуальна, но всё равно медленно","next":"sol_deep"}]},"system":{"kind":"step","title":"Вся система: где узкое место?","detail":"Если медленно всё, а не один запрос, ищите узкое место по ресурсам: CPU, IO, память или массовые блокировки?","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 близок к 100%","next":"sol_cpu"},{"label":"Высокое ожидание IO","next":"sol_io"},{"label":"Давление на память / низкий процент попаданий в кэш","next":"sol_mem"},{"label":"Много блокировок","next":"sol_blocking"}]},"sol_blocking":{"kind":"solution","title":"Первопричина: блокировки","detail":"Запрос ждёт блокировку, удерживаемую другой сессией. Найдите сессию во главе цепочки (корневой блокировщик) и устраните её.","steps":["Найдите начало цепочки блокировок (сессию, которая сама не ждёт, но заставляет ждать других).","Поймите, что делает корневой блокировщик: долгая незафиксированная транзакция или 'idle in transaction'?","Если приложение может выполнить commit/rollback, инициируйте это; если нет и работа стоит, завершите корневой блокировщик.","Устраните первопричину: недостающий индекс может вызывать широкие блокировки; сократите транзакции и пересмотрите уровень изоляции."],"related":["blocking-sessions","kill-session"],"playbook":"blocking"},"sol_io":{"kind":"solution","title":"Первопричина: узкое место IO","detail":"Запрос много читает с диска. Обычно из-за полных сканирований (нет индекса) или нехватки памяти (буферный кэш).","steps":["Добавьте подходящий индекс для фильтра, вызывающего полное сканирование (план должен перейти на сканирование индекса, IXSCAN).","Проверьте hit ratio буферного кэша / buffer pool; если он низкий, увеличьте память.","Сократите лишние чтения столбцов/строк (нужные столбцы вместо SELECT *, постраничный вывод).","Если хранилище действительно медленное (высокая задержка IO), улучшите дисковый уровень."],"related":["cache-hit-ratio","create-index"]},"sol_log":{"kind":"solution","title":"Первопричина: узкое место commit / журнала","detail":"Если преобладает 'log file sync' / 'WRITELOG', фиксации ждут записи redo/WAL.","steps":["Превратите частые однострочные фиксации в пакетные; сократите лишние фиксации.","Ускорьте диск redo/WAL (отдельный быстрый диск); пересмотрите размер/число файлов журнала.","Оцените настройки synchronous_commit / commit_write с учётом нагрузки и компромисса по надёжности."],"related":["show-config-param"]},"sol_index":{"kind":"solution","title":"Первопричина: нет индекса","detail":"План выполняет полное сканирование. Подходящий индекс по столбцам фильтра/соединения даёт большое ускорение.","steps":["Онлайн добавьте подходящий индекс (при необходимости составной) по столбцам WHERE/JOIN.","После добавления индекса снова получите план и убедитесь, что он перешёл на сканирование индекса (IXSCAN).","Если нагрузка на запись высокая, взвесьте стоимость индекса; удалите ненужные индексы.","Индекс не помогает на столбцах с низкой селективностью; проверьте селективность."],"related":["create-index","explain-plan"]},"sol_planchange":{"kind":"solution","title":"Первопричина: регрессия плана","detail":"Вчера запрос был быстрым, а сегодня нет; план изменился. Обычно из-за устаревшей статистики или изменения параметров.","steps":["Сначала соберите свежую статистику (устаревшая статистика — самая частая причина).","Зафиксируйте хороший план: Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan', настройки PG.","Если недавно было обновление/патч/изменение параметров, проверьте его (поведение оптимизатора)."],"related":["update-statistics"]},"sol_rewrite":{"kind":"solution","title":"Первопричина: дизайн запроса","detail":"План разумен, но запрос по своей сути делает слишком много работы (слишком много строк, N+1, лишние соединения).","steps":["Уменьшите результирующий набор: постраничный вывод (LIMIT/OFFSET или keyset), ранняя фильтрация.","Превратите шаблон N+1 в один пакетный запрос; упростите лишние соединения/подзапросы.","Для повторяющихся тяжёлых отчётов рассмотрите материализованное представление / сводную таблицу."],"related":["explain-plan","materialized-view"]},"sol_stats":{"kind":"solution","title":"Первопричина: устаревшая статистика","detail":"Оптимизатор ошибается в оценках, потому что статистика не актуальна.","steps":["Соберите статистику вручную для затронутых таблиц.","Убедитесь, что задание автоматического сбора (autovacuum/auto stats/gather job) работает.","Добавьте сбор статистики в процесс после внезапных крупных загрузок данных."],"related":["update-statistics"]},"sol_deep":{"kind":"solution","title":"Углублённый анализ","detail":"Если план и статистика в порядке, но всё равно медленно, копайте глубже.","steps":["Включите трассировку на уровне сессии: Oracle 10046, PG auto_explain, MySQL profiling, MSSQL Extended Events.","Изучите параллелизм, предоставление памяти и сбросы sort/hash (tempdb/temp).","Исключите уровень оборудования/ОС (CPU steal, задержка IO, сеть).","Оцените конкуренцию, вызванную числом соединений и параллельностью."],"related":["session-trace","top-wait-events"]},"sol_cpu":{"kind":"solution","title":"Первопричина: узкое место CPU","detail":"Вся система упирается в CPU. Обычно CPU сжигают несколько запросов с плохими планами.","steps":["Найдите топ-10 запросов по CPU и исправьте их планы (индексы/статистика).","Ограничьте вышедшие из-под контроля запросы (Resource Manager / Resource Governor).","Если загрузка остаётся высокой, масштабируйтесь (добавьте CPU, распределите чтение на реплики для чтения)."],"related":["top-slow-queries"],"playbook":"high-cpu"},"sol_mem":{"kind":"solution","title":"Первопричина: давление на память","detail":"Низкий процент попаданий в кэш и/или своппинг: памяти недостаточно или она распределена неправильно.","steps":["Правильно задайте размер buffer pool / SGA и памяти сессий (work_mem/PGA) (Инструменты > Калькулятор памяти).","Найдите и исправьте вышедший из-под контроля запрос, потребляющий память.","Если система свопится, выход из свопа — приоритет; при необходимости добавьте RAM."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"}}},"solver/deadlock.json":{"id":"deadlock","title":"Взаимоблокировка / конфликт блокировок (ORA-00060, 1213, 40P01)","icon":"♻️","category":"Блокировки","intro":"Возникла взаимоблокировка (Oracle ORA-00060, MySQL 1213, PG 40P01, MSSQL 1205). Получите граф, определите шаблон, предотвратите повторение. Взаимоблокировки устраняются на уровне дизайна — цель в снижении их частоты.","start":"capture","nodes":{"capture":{"kind":"step","title":"Сначала получите граф взаимоблокировки","detail":"Какие две (или более) сессии, какие ресурсы, какие режимы блокировок столкнулись? Не применяйте исправления вслепую, не увидев этого.","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":"Граф получен — две транзакции блокируют ресурсы в обратном порядке","next":"sol_order"},{"label":"Много транзакций скапливается на одной строке/таблице (hotspot)","next":"sol_hotspot"},{"label":"Есть широкие блокировки диапазонов (блокируется много строк)","next":"index_check"}]},"index_check":{"kind":"step","title":"Использует ли блокирующий запрос индекс?","detail":"Без индекса UPDATE/DELETE блокирует много строк (даже всю таблицу), чтобы выполнить условие WHERE → источник взаимоблокировок. Проверьте его план.","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":"Индекса нет / выполняется полное сканирование","next":"sol_index"},{"label":"Индекс есть, но конфликт всё равно возникает","next":"sol_order"}]},"sol_order":{"kind":"solution","title":"Первопричина: обратный порядок блокировок","detail":"Самая частая причина взаимоблокировки: две транзакции обращаются к ресурсам в разном порядке. Когда A→B и B→A происходят одновременно, возникает взаимоблокировка.","steps":["Обращайтесь к ресурсам в ОДНОМ И ТОМ ЖЕ порядке во всех ветках кода (например, всегда от меньшего id к большему).","Делайте транзакции короткими; выносите взаимодействие с пользователем/долгую работу за пределы транзакции.","При необходимости берите блокировки заранее в детерминированном порядке (упорядоченный SELECT ... FOR UPDATE).","Добавьте в приложение автоматический повтор (retry + backoff) для жертвы взаимоблокировки — это обязательно."],"related":["blocking-sessions","open-transactions"]},"sol_index":{"kind":"solution","title":"Первопричина: нет индекса → широкие блокировки","detail":"UPDATE/DELETE без индекса блокирует больше строк, чем нужно, и делает взаимоблокировки гораздо вероятнее.","steps":["Добавьте индекс по столбцу в условии WHERE; тогда блокировка ограничится затронутыми строками.","Снова получите план и убедитесь, что полное сканирование (Seq Scan / type=ALL) сменилось доступом по индексу.","Выполняйте массовые обновления небольшими пакетами (chunks)."],"related":["create-index","list-indexes"]},"sol_hotspot":{"kind":"solution","title":"Первопричина: горячая точка (hotspot)","detail":"Много транзакций одновременно скапливается на одном ресурсе, например строке/ключе счётчика.","steps":["Распределите горячую строку: используйте sequence/identity, разделите счётчики (sharded counter).","Сократите длительность транзакции, чтобы блокировка удерживалась меньше.","По возможности используйте оптимистическую блокировку (version/timestamp) или шаблон очереди."],"related":["sequence","open-transactions"]}}},"solver/disk-full.json":{"id":"disk-full","title":"Диск / табличное пространство заполнено","icon":"💽","category":"Ёмкость","intro":"Вы получили 'unable to extend', 'disk full', 'could not extend' или 'log full'. Без паники: сначала найдите, ЧТО заполнено, безопасно освободите место, затем устраните проблему окончательно.","start":"what","nodes":{"what":{"kind":"step","title":"Что заполнено?","detail":"Чистка не того места не поможет. Сначала измерьте использование: это файл данных, журнал транзакций, archive/redo, temp или диск ОС?","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":"Заполнен файл данных / табличное пространство","next":"data"},{"label":"Заполнен журнал транзакций / archive-redo","next":"log"},{"label":"Заполнено temp / undo","next":"sol_temp"},{"label":"Заполнен диск ОС (не БД)","next":"sol_os"}]},"data":{"kind":"step","title":"Кто главный потребитель?","detail":"Чаще всего виновник — одна разросшаяся таблица/сегмент. Найдите самые большие.","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":"Есть неожиданно большая/разросшаяся таблица","next":"sol_runaway"},{"label":"Нормальный рост, место действительно закончилось","next":"sol_extend"},{"label":"Слишком много мёртвых строк / bloat","next":"sol_reclaim"}]},"log":{"kind":"step","title":"Почему растёт журнал?","detail":"Журнал/архив нельзя очистить, пока не завершится самая старая активная транзакция/резервное копирование. Почему он накопился?","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_BACKUP)","next":"sol_logbackup"},{"label":"PG: зависший слот репликации удерживает WAL","next":"sol_slot"},{"label":"Oracle: область архивных журналов (FRA) заполнена","next":"sol_archive"},{"label":"Долгая открытая транзакция удерживает журнал","next":"sol_longtxn"}]},"sol_runaway":{"kind":"solution","title":"Первопричина: разросшаяся таблица/сегмент","detail":"Один объект неожиданно вырос (неконтролируемые вставки, таблица очереди/журнала, которая никогда не очищается).","steps":["Проверьте, действительно ли объект нужен; если это таблица журнала/очереди, примените срок хранения.","Удалите ненужные старые данные через удаление секции / TRUNCATE (после бэкапа/согласования).","Остановите неконтролируемый рост в приложении; настройте оповещение о росте."],"related":["largest-tables","create-partition"]},"sol_extend":{"kind":"solution","title":"Первопричина: реальная потребность в ёмкости","detail":"Нормальный рост; нужно добавить место.","steps":["Добавьте место в файл данных или включите AUTOEXTEND (Oracle: ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE).","При необходимости добавьте новый файл данных/табличное пространство или увеличьте диск.","Настройте оповещения при заполнении 80%/90%, чтобы больше не было сюрпризов."],"related":["free-space"]},"sol_reclaim":{"kind":"solution","title":"Первопричина: bloat / мёртвые строки","detail":"Место удалённых/обновлённых строк не освобождено.","steps":["Освободите место: PG VACUUM (при необходимости pg_repack), Oracle SHRINK SPACE, MySQL OPTIMIZE TABLE, Mongo compact.","Сделайте настройки autovacuum/автоматического обслуживания более агрессивными.","VACUUM FULL / OPTIMIZE блокируют таблицу — запускайте их вне часов пик."],"related":["reclaim-space","free-space"]},"sol_temp":{"kind":"solution","title":"Первопричина: заполнено temp / undo","detail":"Большая сортировка/хеширование или долгая открытая транзакция заполнили временное пространство.","steps":["Найдите запрос, раздувший temp (большой сброс sort/hash), и оптимизируйте его.","Найдите долгую открытую транзакцию; именно она удерживает undo (Oracle).","При необходимости увеличьте пространство temp/undo; пересмотрите настройку retention."],"related":["open-transactions","reclaim-space"]},"sol_os":{"kind":"solution","title":"Первопричина: заполнен диск ОС","detail":"Проблема на диске операционной системы, а не в БД. Место могут занимать журналы, core dump, старые бэкапы или файлы трассировки.","steps":["Найдите заполненную файловую систему через df -h; найдите самые большие каталоги (du -sh *).","Очистите старые файлы журналов/трассировки/core dump/бэкапов (с согласованием).","Настройте ротацию для диагностических каталогов БД (Oracle ADR, журналы PG)."]},"sol_logbackup":{"kind":"solution","title":"Первопричина: не делаются резервные копии журнала (SQL Server)","detail":"В модели восстановления FULL журнал растёт бесконечно, если не делать его резервные копии.","steps":["Сделайте резервную копию журнала (BACKUP LOG) — это делает журнал снова пригодным для повторного использования.","Если модель восстановления действительно должна быть SIMPLE, измените её.","Сжимайте файл журнала только при необходимости (DBCC SHRINKFILE) и настройте регулярное задание резервного копирования журнала."],"related":["last-backup-status"],"playbook":"log-growth"},"sol_slot":{"kind":"solution","title":"Первопричина: брошенный слот репликации (PG)","detail":"Зависший (неактивный) слот мешает удалению WAL и раздувает pg_wal.","steps":["Проверьте, действительно ли слот используется.","Если он не нужен, удалите его: SELECT pg_drop_replication_slot('slot_name');","Почините активную реплику, чтобы слот продвигался; настройте оповещение об отставании слота."],"related":["free-space"],"playbook":"log-growth"},"sol_archive":{"kind":"solution","title":"Первопричина: заполнена область архивных журналов (FRA) (Oracle)","detail":"Архивные журналы накопились, и FRA заполнена; как только архиватор останавливается, база может перестать принимать запись.","steps":["Удалите через RMAN старые архивные журналы, уже сохранённые в бэкап (НИКОГДА не удаляйте их без бэкапа).","При необходимости увеличьте размер FRA (db_recovery_file_dest_size).","Настройте регулярный запуск задания очистки/резервного копирования архивных журналов."],"related":["last-backup-status"]},"sol_longtxn":{"kind":"solution","title":"Первопричина: долгая открытая транзакция","detail":"Самая старая активная транзакция мешает освобождению журнала/undo.","steps":["Найдите самую старую открытую транзакцию и добейтесь её commit/rollback (или поговорите с её владельцем).","Исправьте поведение приложения, оставляющее сессии в 'idle in transaction'; задайте таймаут.","Перенесите долгие отчёты на отдельную реплику/в отдельное окно."],"related":["open-transactions"],"playbook":"log-growth"}}},"solver/connection-storm.json":{"id":"connection-storm","title":"Шторм подключений (слишком много соединений)","icon":"🌊","category":"Ёмкость","intro":"Вы получили 'too many connections', 'remaining connection slots reserved' или ORA-00018; новые соединения отклоняются. Найдите источник, снимите давление сейчас, устраните окончательно.","start":"measure","nodes":{"measure":{"kind":"step","title":"Оцените ситуацию: сколько соединений и сколько из них активны?","detail":"Сначала посмотрите на общее число соединений, лимит и соотношение активных/простаивающих.","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":"Большинство соединений idle / idle in transaction","next":"sol_idle"},{"label":"Большинство активны; нужно найти источник","next":"who"}]},"who":{"kind":"step","title":"Кто/откуда? Сгруппируйте соединения по источнику","detail":"Обычно раздулся пул одного сервера приложений или есть утечка соединений. Посмотрите на распределение по хосту/пользователю/приложению.","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":"Соединения раздуло одно приложение/хост (пул)","next":"sol_pool"},{"label":"Нагрузка действительно выросла; все соединения легитимны и активны","next":"sol_scale"}]},"sol_idle":{"kind":"solution","title":"Первопричина: скопление простаивающих соединений / утечка","detail":"Соединения открываются и никогда не закрываются или остаются в 'idle in transaction'; они впустую занимают слоты.","steps":["Закройте долго простаивающие / idle-in-transaction соединения; используйте слот, зарезервированный для администраторов.","Задайте таймауты на стороне сервера: PG idle_in_transaction_session_timeout, MySQL wait_timeout, Oracle profile idle_time.","Убедитесь, что приложение закрывает соединения (и транзакции); устраните утечку в источнике."],"related":["open-transactions","kill-session","connection-count"],"playbook":"connection-storm"},"sol_pool":{"kind":"solution","title":"Первопричина: раздулся пул приложения","detail":"Пул соединений сервера приложений не ограничен/слишком велик или течёт. Именно он порождает соединения.","steps":["Немедленно: завершите простаивающие соединения проблемного приложения и перезапустите его пул.","Задайте разумный размер пула на стороне приложения (Инструменты > Калькулятор пула соединений).","Поставьте пулер между сервером и БД (PgBouncer / ProxySQL / Oracle DRCP / Connection Manager (CMAN)).","Не увеличивайте max_connections вслепую; это расходует память и лишь откладывает проблему."],"related":["connection-count","kill-session"],"playbook":"connection-storm"},"sol_scale":{"kind":"solution","title":"Первопричина: реальный рост нагрузки","detail":"Соединения легитимны и активны; ёмкости действительно не хватает.","steps":["Сократите число соединений, доходящих до БД, мультиплексируя их через пулер соединений.","Распределите нагрузку чтения по репликам для чтения.","При необходимости контролируемо увеличьте max_connections в соответствии с бюджетом памяти; увеличьте и память сервера."],"related":["connection-count"],"playbook":"connection-storm"}}},"solver/replication-lag.json":{"id":"replication-lag","title":"Отставание / остановка репликации","icon":"🔁","category":"Репликация","intro":"Standby/реплика отстаёт от primary, или репликация полностью остановилась. Это дерево шаг за шагом сужает тип отставания (растёт, постоянное или остановка), чтобы дойти до первопричины: ошибка применения, узкое место однопоточного применения, конфликт восстановления или повреждение.","start":"root","nodes":{"root":{"kind":"step","title":"Как ведёт себя отставание?","detail":"Сначала оцените ситуацию. Посмотрите на поток реплики и метрику отставания: отставание растёт со временем, застыло на постоянном уровне или применение полностью остановилось? Это делит расследование на три основные ветки.","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":"Применение полностью остановлено / есть ошибка","next":"stopped"},{"label":"Отставание продолжает расти (не может догнать)","next":"growing"},{"label":"Отставание постоянное, но подозревается долгий запрос","next":"standby_query"},{"label":"Сильно отстаёт / выглядит несогласованной","next":"sol_rebuild"}]},"stopped":{"kind":"step","title":"Что говорит ошибка применения?","detail":"Если репликация остановилась, обычно в применяемом событии есть ошибка. Посмотрите на сообщение об ошибке: на цели нет строки/таблицы, конфликт первичного ключа или обрыв соединения/сети?","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":"Ошибка конфликтующей/отсутствующей строки или таблицы","next":"sol_apply_error"},{"label":"Сеть/соединение оборвалось, поток IO остановлен","next":"sol_connection"}]},"growing":{"kind":"step","title":"Почему не может догнать?","detail":"Если отставание продолжает расти, реплика не успевает за скоростью записи на primary. Узкое место — однопоточное применение или медленный диск/IO реплики? Посмотрите на состояние рабочего процесса применения и ожидания IO.","command":{"mysql":"SHOW REPLICA STATUS\\G  -- Seconds_Behind_Source and apply thread state\nSELECT * FROM performance_schema.replication_applier_status_by_worker;","postgres":"SELECT pid, wait_event_type, wait_event, state\nFROM pg_stat_activity WHERE backend_type='startup';","mssql":"SELECT database_id, redo_queue_size, redo_rate, log_send_queue_size\nFROM sys.dm_hadr_database_replica_states;"},"choices":[{"label":"Однопоточное применение + большая нагрузка на запись","next":"sol_parallel"},{"label":"Медленный диск/IO реплики","next":"sol_io"}]},"standby_query":{"kind":"step","title":"Есть ли долгий запрос на standby?","detail":"Долгий запрос на standby только для чтения может блокировать применение (recovery). В PostgreSQL так возникает конфликт восстановления. Посмотрите на активные запросы и счётчики конфликтов на 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":"Да, долгий запрос блокирует применение","next":"sol_recovery_conflict"},{"label":"Нет, запроса нет, но отставание всё равно есть","next":"sol_parallel"}]},"sol_apply_error":{"kind":"solution","title":"Первопричина: ошибка применения (конфликт данных)","detail":"Реплика застряла на событии, которое не может применить: на цели нет строки, конфликт первичного ключа или отсутствует таблица. Устраните причину несогласованности вместо слепого пропуска.","steps":["Изучите ошибку и застрявшее событие: какая таблица и какая операция (delete/update/insert) завершилась ошибкой?","Найдите первопричину: данные, вручную записанные на цель, отсутствующий DDL (таблица/столбец) или повреждённая запись binlog/WAL.","Выровняйте схему: добавьте недостающую таблицу/столбец; сравните конфликтующую строку с primary и исправьте её.","Если событие нужно пропустить (например, одно событие в MySQL), задокументируйте это и затем проверьте согласованность данных реплики.","Предотвратите повторение: запретите прямую запись на цель и выполняйте DDL через primary."],"related":["replication-status"],"playbook":"replication-lag"},"sol_connection":{"kind":"solution","title":"Первопричина: обрыв соединения / сети","detail":"Поток-получатель (IO/receiver) не может подключиться к primary. Проблема может быть в сети, аутентификации или накопленных/удалённых сегментах журнала.","steps":["Проверьте сетевой доступ между primary и репликой и привилегии пользователя репликации.","Проверьте, что нужные сегменты WAL/binlog ещё существуют на primary (retention/expire).","В PostgreSQL проверьте, что слот репликации существует и активен; без слота WAL мог быть удалён раньше времени.","Перезапустите соединение; как только журналы снова пойдут, отставание должно закрыться."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_parallel":{"kind":"solution","title":"Первопричина: узкое место однопоточного применения","detail":"Пока primary пишет параллельно, реплика применяет изменения в одной последовательности и не успевает. Распараллельте применение.","steps":["Включите параллельное применение: в MySQL — replica_parallel_workers + replica_parallel_type=LOGICAL_CLOCK.","В физической репликации PostgreSQL применение выполняет один процесс; снизьте скорость генерации на primary, разбивая большие транзакции.","Перенесите тяжёлые пакетные задания на часы низкого трафика или разбейте их.","После изменения следите за метрикой отставания; она должна снизиться до нуля."],"related":["replication-status"],"playbook":"replication-lag"},"sol_io":{"kind":"solution","title":"Первопричина: узкое место IO на реплике","detail":"Поток применения ждёт диск, а не CPU. Хранилище реплики не успевает за скоростью записи primary.","steps":["Измерьте задержку/насыщение IO диска реплики; сравните с primary.","Если оборудование реплики слабее (медленный диск, меньше RAM), приведите его к уровню primary; асимметричное оборудование — частая причина.","Ускорьте диск для применения redo/WAL; задайте достаточный размер буфера/кэша.","Если не помогает, снизьте нагрузку на запись или распределите трафик чтения на другую реплику."],"related":["replication-status"],"playbook":"replication-lag"},"sol_recovery_conflict":{"kind":"solution","title":"Первопричина: конфликт восстановления на standby","detail":"Долгий запрос только для чтения на standby блокирует применение изменений с primary (конфликт vacuum/блокировки). PostgreSQL решает это, задерживая применение или отменяя запрос.","steps":["Найдите долгий блокирующий запрос; если он действительно нужен, дождитесь завершения, иначе завершите его.","С hot_standby_feedback=on заставьте primary учитывать запросы standby (ценой bloat на primary).","Либо настройте max_standby_streaming_delay под нагрузку (баланс между допустимым отставанием и отменой запросов).","Перенесите долгие отчёты на отдельную реплику, где они не будут блокировать применение, или на другое время."],"related":["replication-status","open-transactions"],"playbook":"replication-lag"},"sol_rebuild":{"kind":"solution","title":"Первопричина: реплика слишком отстала / повреждена — пересоздайте её","detail":"Реплика отстала слишком сильно для восстановления, нужные журналы удалены или данные несогласованы. Самый безопасный путь — пересоздать её из свежего бэкапа.","steps":["Сделайте актуальный базовый бэкап с primary: PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate.","Восстановите бэкап на реплику и заново настройте конфигурацию репликации (слот/пользователь/позиция).","Запустите репликацию и следите за ходом начальной синхронизации (catch-up).","После того как отставание достигнет нуля, проверьте согласованность данных и трафик чтения, затем введите её в эксплуатацию."],"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":"Ошибка Oracle 'ORA-01555: snapshot too old' означает, что старые данные (undo), нужные запросу для согласованного чтения, уже перезаписаны. Это дерево сужает источник ошибки (долгий запрос + недостаточный UNDO, высокий оборот undo или LOB) и ведёт к правильному решению.","start":"root","nodes":{"root":{"kind":"step","title":"Где возникает ошибка?","detail":"Сначала определите контекст ошибки. Классическая ORA-01555 возникает во время долгого SELECT/отчёта или при обращении к столбцу LOB (CLOB/BLOB)? ORA-01555, связанная с LOB, — другой механизм (PCTVERSION/RETENTION) и решается отдельно.","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":"Во время долгого запроса/отчёта","next":"undo_size"},{"label":"При обращении к столбцу LOB","next":"sol_lob"}]},"undo_size":{"kind":"step","title":"Достаточен ли UNDO, подходит ли retention?","detail":"Долгий запрос читает из UNDO старый образ блоков, изменённых после его начала. Если табличное пространство UNDO мало или undo_retention короче длительности запроса, старый undo перезаписывается. Измерьте использование UNDO и 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 мал / retention короче запроса","next":"sol_undo"},{"label":"UNDO достаточен, но фиксации очень частые","next":"sol_commits"},{"label":"Запрос выполняется чрезмерно долго","next":"sol_query"}]},"sol_undo":{"kind":"solution","title":"Первопричина: недостаточный UNDO / retention","detail":"Старый undo, нужный долгому запросу, перезаписан, потому что истёк срок retention или заполнилось табличное пространство. Увеличьте UNDO и задайте retention, покрывающий длительность запроса.","steps":["По самой долгой длительности запроса (v$undostat.maxquerylen) поднимите undo_retention выше неё: ALTER SYSTEM SET undo_retention=10800;","Увеличьте табличное пространство UNDO настолько, чтобы вместить retention, или включите autoextend: ALTER DATABASE DATAFILE '...' AUTOEXTEND ON MAXSIZE ...;","Гарантируйте его для критичных долгих запросов: ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;  (осторожно: если UNDO заполнится, DML может завершаться ошибкой).","После изменения повторите тот же отчёт и убедитесь, что ORA-01555 больше нет."],"related":["show-config-param"],"playbook":"ora-01555"},"sol_commits":{"kind":"solution","title":"Первопричина: высокий оборот undo (частые фиксации)","detail":"Интенсивный DML с очень частыми фиксациями быстро переиспользует блоки undo и стирает старый образ, нужный долгому запросу. Эффект 'commit cleanout' усугубляет это.","steps":["Сократите излишне частые фиксации: используйте пакетные фиксации вместо фиксации каждой строки в цикле.","Чтобы замедлить оборот undo, не планируйте интенсивные DML-нагрузки одновременно с долгим отчётом.","Увеличьте undo_retention и при необходимости защитите старый undo через RETENTION GUARANTEE.","Перенесите отчёт за пределы окна интенсивной записи или привяжите его к точке согласованного чтения."],"related":["open-transactions","show-config-param"],"playbook":"ora-01555"},"sol_query":{"kind":"solution","title":"Первопричина: чрезмерная длительность запроса","detail":"Запрос выполняется так долго, что даже разумного undo_retention недостаточно. Постоянное решение — ускорить запрос или перенести его нагрузку.","steps":["Оптимизируйте запрос: устраните недостающие индексы, плохие планы или лишние полные сканирования; сократите его длительность.","Разбейте большое задание (по дате/диапазону), чтобы каждая часть укладывалась в окно undo.","Перенесите тяжёлый отчёт на standby/реплику или в отдельное время чтения — чтобы он не сталкивался с активным DML.","Когда длительность станет приемлемой, задайте undo_retention соответственно."],"related":["explain-plan"],"playbook":"ora-01555"},"sol_lob":{"kind":"solution","title":"Первопричина: LOB retention / PCTVERSION","detail":"ORA-01555 при чтении LOB означает, что пространства для хранения старых версий LOB (RETENTION или PCTVERSION) недостаточно. У LOB отдельный механизм версионирования.","steps":["Посмотрите текущую настройку столбца LOB: SELECT retention, pctversion FROM dba_lobs WHERE table_name='...' AND column_name='...';","Поднимите соответствующую настройку в зависимости от режима undo AUTO/MANUAL: ALTER TABLE t MODIFY LOB (my_column) (RETENTION);  или увеличьте PCTVERSION в режиме MANUAL.","LOB с RETENTION опираются на значение undo_retention базы — поддерживайте и его достаточным.","Сократите долгие операции с обращением к LOB; после изменения повторно проверьте ошибку."],"related":["show-config-param"],"playbook":"ora-01555"}}},"solver/backup-restore.json":{"id":"backup-restore","title":"Решение по бэкапу и восстановлению","icon":"💾","category":"Бэкап и восстановление","intro":"Дерево решений, которое шаг за шагом проводит через нужные действия с бэкапами (сделать бэкап, восстановить, вернуться к определённому моменту времени — PITR, или проверить бэкап) и выводит на правильные команды и шаги для каждой СУБД.","start":"root","nodes":{"root":{"kind":"step","title":"Что нужно сделать?","detail":"Выберите цель. Вы делаете новый бэкап, восстанавливаете существующий, возвращаетесь к определённому времени (непосредственно перед сбоем) или проверяете, что имеющийся бэкап исправен?","choices":[{"label":"Хочу СДЕЛАТЬ бэкап","next":"take"},{"label":"Хочу ВОССТАНОВИТЬ бэкап","next":"restore"},{"label":"Вернуться к определённому моменту времени (PITR)","next":"pitr"},{"label":"Хочу ПРОВЕРИТЬ бэкап","next":"verify"}]},"take":{"kind":"step","title":"Какой тип бэкапа?","detail":"Логический бэкап (dump) переносим и подходит для выборочного восстановления, но медленный на больших данных. Физический бэкап (уровень файлов/блоков) подходит для больших баз и быстрого восстановления. Выбирайте по потребностям.","choices":[{"label":"Логический бэкап (dump / export)","next":"sol_logical_backup"},{"label":"Физический / полный бэкап","next":"sol_physical_backup"}]},"sol_logical_backup":{"kind":"solution","title":"Логический бэкап (dump)","detail":"Выгружает данные в виде команд SQL/BSON. Идеален для переноса между версиями/платформами и восстановления одной таблицы/коллекции.","steps":["PostgreSQL: pg_dump -Fc -d mydb -f backup.dump  (pg_dumpall для всего кластера).","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: сделайте снимок RDB в фоне через BGSAVE (dump.rdb).","Elasticsearch: сначала зарегистрируйте репозиторий снимков, затем PUT _snapshot/my_repo/snap1?wait_for_completion=true","Перенесите бэкап в безопасное место и запишите его размер/статус завершения."],"related":["logical-dump","last-backup-status"],"playbook":"backup-restore"},"sol_physical_backup":{"kind":"solution","title":"Физический / полный бэкап","detail":"Копия файлов данных на уровне блоков/файлов. Быстро для больших баз и основа для PITR и полного восстановления.","steps":["Oracle: RMAN> BACKUP DATABASE PLUS ARCHIVELOG;  (вместе с архивными журналами).","PostgreSQL: pg_basebackup -D /backup/base -Fp -Xs -P  (с включённым архивированием WAL).","SQL Server: BACKUP DATABASE [mydb] TO DISK='D:\\backup\\db.bak' WITH CHECKSUM, COMPRESSION;","MySQL/MariaDB: xtrabackup --backup --target-dir=/backup/base  (согласованная физическая копия).","MongoDB: сделайте снимок файловой системы или согласованный снимок с участника набора реплик.","Скопируйте бэкап в офлайн/удалённое место и запишите статус последнего бэкапа."],"related":["last-backup-status"],"playbook":"backup-restore"},"restore":{"kind":"solution","title":"Восстановление бэкапа","detail":"Восстановите на цель наиболее подходящий имеющийся бэкап. Сначала проверьте целостность и метку времени бэкапа, затем загрузите его подходящей для СУБД командой.","steps":["Проверьте дату и исправность восстанавливаемого бэкапа (знайте своё окно потери данных).","PostgreSQL: pg_restore -d new_db backup.dump   (для SQL-дампа: psql -d new_db -f backup.sql).","Oracle: RMAN> RESTORE DATABASE; RECOVER DATABASE; ALTER DATABASE OPEN;  (логический импорт через impdp).","MySQL/MariaDB: mysql mydb < backup.sql   (физический: xtrabackup --copy-back).","SQL Server: RESTORE DATABASE [mydb] FROM DISK='D:\\backup\\db.bak' WITH RECOVERY, REPLACE;","MongoDB: mongorestore --db mydb /backup/dir","После восстановления проверьте данные по числу строк/проверками приложения."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"pitr":{"kind":"solution","title":"Возврат к определённому моменту времени (PITR)","detail":"Нужно вернуться с точностью до секунды к моменту непосредственно перед случайным удалением/повреждением. Нужен полный бэкап + журналы транзакций до этого момента (архивные журналы / WAL / binlog).","steps":["Точно определите целевое время (непосредственно перед инцидентом) и проверьте, что нужные журналы доступны.","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: восстановите базовый бэкап, затем задайте recovery_target_time='2026-07-04 10:00:00' и примените WAL до этого момента.","SQL Server: RESTORE ... WITH NORECOVERY; затем RESTORE LOG ... WITH STOPAT='2026-07-04 10:00:00', RECOVERY;","MySQL/MariaDB: восстановите полный бэкап, затем примените binlog до этого момента через mysqlbinlog --stop-datetime='2026-07-04 10:00:00'.","После открытия убедитесь, что данные соответствуют ожидаемому моменту времени."],"related":["restore-dump","last-backup-status"],"playbook":"backup-restore"},"verify":{"kind":"solution","title":"Проверка бэкапа","detail":"Бэкап становится бэкапом только тогда, когда доказано, что его можно восстановить. Регулярно проверяйте целостность и фактическую восстанавливаемость.","steps":["Oracle: RMAN> RESTORE DATABASE VALIDATE;  а для согласованности блоков VALIDATE CHECK LOGICAL DATABASE;","SQL Server: делайте бэкапы WITH CHECKSUM и выполняйте RESTORE VERIFYONLY ... WITH CHECKSUM (проверяет только файл бэкапа); для настоящего доказательства восстановите бэкап на тестовый сервер и выполните там DBCC CHECKDB([mydb]) WITH NO_INFOMSGS; (CHECKDB проверяет работающую/восстановленную базу, а не файл бэкапа).","PostgreSQL: реально восстановите бэкап на отдельный/временный сервер и убедитесь, что pg_restore завершается без ошибок.","MySQL/MongoDB: восстановите дамп в тестовую среду и сравните число строк/коллекций и выборочные данные.","Планируйте регулярные 'учения по восстановлению' — делать бэкапы недостаточно, докажите и восстановление.","Запишите результаты и дату последней успешной проверки."],"related":["last-backup-status","restore-dump"],"playbook":"backup-restore"}}},"solver/upgrade-wizard.json":{"id":"upgrade-wizard","title":"Мастер обновления версии","icon":"⬆️","category":"Жизненный цикл","intro":"Патч или мажорное обновление, на месте или через миграцию? Спланируйте обновление шаг за шагом; каждая ветка охватывает предварительные проверки, метод и работы после обновления. Отрепетируйте здесь, прежде чем трогать продакшен.","start":"kind","nodes":{"kind":{"kind":"step","title":"Какое обновление вы выполняете?","detail":"Патч/минорная версия (в пределах одной мажорной версии, например 16.2 -> 16.4, или CU для SQL Server) — низкий риск, обычно выполняется на месте (in-place). Мажорная версия (например, 15 -> 16) может включать изменения формата данных и требует более тщательного планирования.","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":"Минорная версия / патч (та же мажорная версия, CU)","next":"patch_prep"},{"label":"Мажорная версия (меняется первое число версии)","next":"major_method"},{"label":"Не знаю разницы; объясните","next":"explain"}]},"explain":{"kind":"step","title":"Патч или мажорное?","detail":"Патч: мажорная версия не меняется (16.2 -> 16.4); формат файлов данных не меняется, обычно замена бинарников + перезапуск. Мажорное: меняется мажорная версия (15 -> 16); каталог/формат может измениться, нужен pg_upgrade / AutoUpgrade / dump-restore, и необходимо протестировать совместимость приложения.","choices":[{"label":"У меня патч (та же мажорная версия)","next":"patch_prep"},{"label":"У меня мажорное (мажорная версия меняется)","next":"major_method"}]},"patch_prep":{"kind":"step","title":"Готовы ли бэкап и план отката?","detail":"Хотя патч и малорисков, золотое правило любого обновления: сначала полный бэкап (и по возможности снимок). Нужна точка возврата на случай, если что-то пойдёт не так.","choices":[{"label":"Да, бэкап + откат готовы","next":"sol_patch"},{"label":"Нет, ещё нет","next":"sol_backupfirst"}]},"sol_backupfirst":{"kind":"solution","title":"Сначала бэкап + план отката","detail":"Подготовьте страховку до начала обновления.","steps":["Сделайте полный бэкап (и по возможности снимок хранилища / Oracle guaranteed restore point).","Запишите шаги отката: если что-то пойдёт не так, как вернуться к старой версии?","Утвердите окно обслуживания и план коммуникации, затем переходите к шагу патча."],"related":["logical-dump","last-backup-status"]},"sol_patch":{"kind":"solution","title":"Установка патча","detail":"Патч в пределах той же мажорной версии. Поочерёдная установка по кластеру (начиная с реплик) сокращает простой.","steps":["Oracle: установите новый RU через opatch, затем примените изменения SQL через 'datapatch -verbose'.","PostgreSQL: минорное обновление = установить новые бинарники и перезапустить службу (initdb/dump не нужны).","MySQL: обновите пакет; в 8.0 сервер выполняет обновление автоматически при запуске.","SQL Server: запустите установщик соответствующего CU/GDR, затем перезапустите.","MongoDB/Redis/Elasticsearch: поочерёдный перезапуск; сначала вторичные узлы, первичный последним.","Затем проверьте версию и приложение; при ошибках выполните план отката."],"related":["apply-patch","check-version"],"playbook":"upgrade-rollback"},"major_method":{"kind":"step","title":"Метод мажорного обновления","detail":"Два основных пути: (1) На месте: конвертация на существующем сервере (pg_upgrade, Oracle AutoUpgrade/DBUA, MySQL in-place). Быстро, но откатиться сложно. (2) Через миграцию: поднимите новый сервер на новой версии, перенесите данные (через dump/restore или репликацию), затем переключитесь. Безопаснее, с минимальным простоем.","choices":[{"label":"Сначала выполню проверку подготовки/совместимости","next":"precheck"},{"label":"На месте","next":"sol_inplace"},{"label":"Через миграцию на новый сервер (минимальный простой)","next":"sol_migrate"}]},"precheck":{"kind":"step","title":"Проверка совместимости / готовности","detail":"Перед обновлением соберите предупреждения целевой версии: удалённые (устаревшие) возможности, недействительные объекты, уровень совместимости.","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":"Вывод без ошибок — продолжаем","next":"major_method"},{"label":"Есть предупреждения/несовместимости","next":"sol_fixwarn"}]},"sol_fixwarn":{"kind":"solution","title":"Сначала устраните предупреждения","detail":"Устраните каждое предупреждение из отчёта ДО обновления; иначе обновление может прерваться на полпути или сломать приложение.","steps":["Обновите объекты и запросы, использующие удалённые (устаревшие) возможности/синтаксис.","Перекомпилируйте/исправьте недействительные объекты; устраните недостающие зависимости.","Спланируйте шаги уровня совместимости / версии возможностей для целевой версии.","После устранения предупреждений повторите предварительную проверку и вернитесь к выбору метода."],"related":["check-upgrade-readiness","set-compatibility"]},"sol_inplace":{"kind":"solution","title":"Мажорное обновление на месте","detail":"Конвертация на существующем сервере. Быстро, но откат обычно означает восстановление из бэкапа, поэтому снимок обязателен.","steps":["Сделайте полный бэкап + снимок/guaranteed restore point (единственный путь назад).","Oracle: AutoUpgrade (режим deploy) или DBUA. PostgreSQL: pg_upgrade (--link быстрый, но без пути назад). MySQL: новые бинарники + автоматическое обновление на месте. SQL Server: setup /ACTION=upgrade.","Выполните задачи после обновления (см. связанные ниже).","Проверьте приложение под реальной нагрузкой; при регрессии планов зафиксируйте поведение оптимизатора."],"related":["upgrade-major","post-upgrade-tasks"],"playbook":"upgrade-rollback"},"sol_migrate":{"kind":"solution","title":"Обновление через миграцию (минимальный простой)","detail":"Поднимите новый сервер на новой версии, перенесите данные, затем переключитесь коротким cutover. Самый безопасный метод; старая система остаётся нетронутой.","steps":["Поднимите новый сервер на целевой версии и подготовьте схему.","Перенесите начальную загрузку через dump/restore или репликацию; затем поддерживайте синхронизацию непрерывной репликацией (CDC / логическая репликация).","В коротком окне обслуживания: остановите запись, дождитесь оставшейся дельты и направьте приложение на новый сервер (cutover).","Проверьте миграцию (число строк/контрольные суммы); при проблеме немедленно переключитесь обратно на старый сервер."],"related":["setup-cdc-replication","verify-migration","export-for-migration"]}}},"solver/migration-wizard.json":{"id":"migration-wizard","title":"Мастер миграции (миграция данных/схемы)","icon":"🚚","category":"Жизненный цикл","intro":"Вы переносите данные/схему. Миграция однородная или разнородная и какова допустимость простоя? Спланируйте шаг за шагом: оценка → миграция → проверка. Сначала отрепетируйте cutover здесь.","start":"kind","nodes":{"kind":{"kind":"step","title":"Откуда и куда вы выполняете миграцию?","detail":"Гомогенная: тот же движок базы данных (например, Oracle 19 → Oracle 23 или PostgreSQL на другой сервер). Гетерогенная: другой движок (например, Oracle → PostgreSQL, MySQL → SQL Server). Гетерогенные миграции требуют гораздо больше работы по совместимости (типы данных, функции, PL/SQL).","choices":[{"label":"Тот же движок, новый сервер/версия (гомогенная)","next":"downtime"},{"label":"На другой движок (гетерогенная)","next":"hetero"},{"label":"Я на этапе планирования; с чего начать?","next":"downtime"}]},"downtime":{"kind":"step","title":"Какой простой допустим?","detail":"Это определяет метод миграции. Если есть окно обслуживания, самый простой путь — dump/restore. Если нужен почти нулевой простой, вы загружаете начальные данные, поддерживаете синхронизацию непрерывной репликацией (CDC) и выполняете короткий cutover.","choices":[{"label":"Есть окно обслуживания (простой допустим)","next":"sol_offline"},{"label":"Нужен почти нулевой простой","next":"sol_online"}]},"hetero":{"kind":"step","title":"Оценили ли вы совместимость схемы и типов?","detail":"В разнородной миграции основная работа — не копирование данных, а устранение различий: типы данных (VARCHAR2↔TEXT), функции (NVL↔COALESCE), значения автоинкремента, PL/SQL ↔ PL/pgSQL, удалённые/отличающиеся возможности.","choices":[{"label":"Я сопоставил различия схемы/типов","next":"sol_hetero"},{"label":"Я ещё не оценивал их","next":"sol_assess"}]},"sol_assess":{"kind":"solution","title":"Сначала оцените совместимость","detail":"Перед миграцией сопоставьте различия между источником и целью; если пропустить этот шаг, миграция застрянет на полпути.","steps":["Подготовьте сравнение схем: таблицы, ограничения, индексы, представления, процедуры.","Сопоставьте типы данных и функции (модуль DBAtlas > Типы и функции создан именно для этого).","Решите, как на цели будут реализованы специфичные для СУБД возможности (identity/sequence, секционирование, PL/SQL, триггеры).","Выберите инструмент конвертации (ora2pg, pgloader, AWS SCT/DMS) и отрепетируйте на небольшой выборке."],"related":["compare-schema","heterogeneous-migration"]},"sol_offline":{"kind":"solution","title":"Офлайн-миграция (dump / restore)","detail":"Самый простой и надёжный однородный метод: сделайте согласованный бэкап, загрузите его в цель, проверьте, переключитесь.","steps":["Сделайте СОГЛАСОВАННЫЙ экспорт из источника (pg_dump -Fc, expdp FLASHBACK_SCN, mysqldump --single-transaction, BACKUP, mongodump --oplog).","Загрузите его в цель (pg_restore -j, impdp, mysql <, RESTORE, mongorestore).","Проверьте миграцию: совпадают ли число строк и критичные контрольные суммы между источником и целью?","В окно обслуживания направьте приложение на цель (cutover); если что-то пойдёт не так, вернитесь к источнику."],"related":["export-for-migration","import-for-migration","verify-migration"]},"sol_online":{"kind":"solution","title":"Минимальный простой (CDC / репликация)","detail":"Загрузите начальные данные, затем поддерживайте синхронизацию цели с источником через непрерывную репликацию; простой возникает только во время короткого cutover.","steps":["Загрузите начальные (базовые) данные через dump/restore или снимок.","Настройте непрерывную репликацию: логическая репликация PG (PUBLICATION/SUBSCRIPTION), репликация binlog MySQL, Oracle GoldenGate, транзакционная репликация/CDC MSSQL, change streams/mongosync в Mongo.","Ждите и наблюдайте, пока цель не догонит источник и отставание не станет ~0.","Короткий cutover: остановите запись в источник, дождитесь оставшейся дельты, направьте приложение на цель, проверьте."],"related":["setup-cdc-replication","verify-migration"]},"sol_hetero":{"kind":"solution","title":"Разнородная миграция (на другую СУБД)","detail":"Конвертируйте схему, перенесите данные, адаптируйте логику, проверьте. Двигайтесь поэтапно; не переносите всё за один раз.","steps":["Конвертируйте схему: инструментом (ora2pg, pgloader, AWS SCT) или вручную; адаптируйте типы и ограничения к цели.","Перенесите данные: массовая загрузка или инструмент; большие таблицы делите на части/чанки.","Адаптируйте серверную логику: PL/SQL → PL/pgSQL, аналоги функций/триггеров.","Тщательно проверьте: число строк, выборочные данные, результаты критичных запросов; протестируйте приложение на цели.","Если простой нужно сократить, после конвертации поддерживайте синхронизацию цели через слой CDC, затем переключитесь."],"related":["heterogeneous-migration","verify-migration","setup-cdc-replication"]}}},"solver/high-cpu.json":{"id":"high-cpu","title":"Высокая загрузка CPU","icon":"🔥","category":"Производительность","intro":"CPU сервера загружен до предела. Сначала убедитесь, что его действительно потребляет база данных, затем пройдите по веткам к первопричине: запросы с плохими планами, регрессия плана, избыточная конкурентность или процесс вне БД.","start":"root","nodes":{"root":{"kind":"step","title":"Действительно ли CPU потребляет база данных?","detail":"Проверьте на стороне операционной системы, какой процесс сжигает CPU. Основная нагрузка в процессах БД или в соседнем процессе (бэкап, антивирус, другое приложение)? Это делит расследование на две ветки.","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":"Да, CPU потребляют процессы БД","next":"db_confirm"},{"label":"Нет, его потребляет соседний процесс / задание бэкапа","next":"sol_os"}]},"db_confirm":{"kind":"step","title":"Как проявилась нагрузка?","detail":"Если CPU потребляет БД, определите шаблон: преобладают несколько тяжёлых запросов, проблема началась внезапно после изменения или есть высокая конкурентность/конкуренция за соединения?","choices":[{"label":"CPU сжигают несколько тяжёлых запросов","next":"topcpu"},{"label":"Началось внезапно; недавно был деплой / изменение параметров","next":"sol_regression"},{"label":"Много соединений / конкуренция параллельных запросов","next":"sol_concurrency"}]},"topcpu":{"kind":"step","title":"Найдите запросы, потребляющие больше всего CPU","detail":"Получите топ запросов по суммарному потреблению CPU. Затем определите, тяжёл ли каждый за один вызов (плохой план) или дёшев за вызов, но вызывается очень часто.","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":"Тяжёлый за вызов; плохой план / полное сканирование","next":"sol_query"},{"label":"Дешёвый за вызов, но вызывается очень часто","next":"sol_frequency"}]},"sol_query":{"kind":"solution","title":"Первопричина: CPU сжигают запросы с плохими планами","detail":"Несколько запросов потребляют избыточный CPU за вызов из-за полных сканирований или плохого порядка соединений. Исправление их планов — основное решение.","steps":["Получите планы (EXPLAIN) запросов с наибольшим потреблением CPU; ищите полные сканирования, плохой порядок соединений или разрывы между оценочным и фактическим числом строк.","Добавьте подходящие индексы по столбцам фильтров/соединений; убедитесь, что план перешёл на сканирование индекса (IXSCAN).","Если оптимизатор ошибается в оценках, соберите свежую статистику.","Ограничьте вышедшие из-под контроля запросы (Oracle Resource Manager / MSSQL Resource Governor) и при необходимости перепишите запрос."],"related":["top-slow-queries","explain-plan","create-index","update-statistics"],"playbook":"high-cpu"},"sol_frequency":{"kind":"solution","title":"Первопричина: избыточная частота вызовов","detail":"По отдельности запросы дешёвые, но вызываются тысячи раз в секунду; отсюда и суммарная стоимость CPU. Обычно это шаблон N+1 или некэшируемый горячий путь.","steps":["Определите самые часто вызываемые команды (calls / execution_count) и найдите вызывающий путь в приложении.","Превратите шаблон N+1 в пакетный запрос; сократите повторные чтения с помощью кэша приложения/результатов.","Используйте подготовленные выражения (prepared statements), чтобы сократить стоимость повторного разбора (hard parse).","Для частых неизменных тяжёлых запросов рассмотрите материализованное представление / сводную таблицу."],"related":["top-slow-queries","explain-plan"],"playbook":"high-cpu"},"sol_regression":{"kind":"solution","title":"Первопричина: регрессия плана / устаревшая статистика","detail":"Вчера система работала нормально; CPU подскочил после деплоя, изменения параметров или загрузки данных. Оптимизатор мог переключиться на худший план.","steps":["Определите изменение: недавний деплой, обновление/патч, изменение параметров или крупная загрузка данных.","Соберите свежую статистику для затронутых таблиц (устаревшая статистика — самая частая причина).","Зафиксируйте хороший план: Oracle SQL Plan Baseline / hint, MSSQL Query Store 'force plan'.","Если изменение связано с параметром, верните его и переоцените контролируемо."],"related":["update-statistics","explain-plan","show-config-param"],"playbook":"high-cpu"},"sol_concurrency":{"kind":"solution","title":"Первопричина: высокая конкурентность / конкуренция за соединения","detail":"Отдельные запросы приемлемы, но одновременно работает слишком много сессий; активных сессий больше, чем ядер CPU, что вызывает переключения контекста и конкуренцию latch/spin.","steps":["Сравните число активных сессий с числом ядер CPU; при активных сессиях больше, чем ядер, образуется очередь выполнения.","Поставьте пулер соединений между приложением и БД (PgBouncer / ProxySQL) и задайте разумный размер пула.","При избыточном параллелизме ограничьте степень параллелизма (Oracle parallel_degree_limit / parallel_max_servers, MSSQL MAXDOP).","Разгрузите CPU первичного узла, распределив нагрузку чтения по репликам для чтения."],"related":["connection-count","active-sessions"],"playbook":"connection-storm"},"sol_os":{"kind":"solution","title":"Первопричина: CPU потребляет процесс вне БД","detail":"CPU потребляет не база данных, а соседний процесс: задание бэкапа/сжатия, антивирусная проверка, отправщик журналов или другое приложение на той же машине.","steps":["Точно определите виновный процесс инструментом ОС (top / Task Manager).","Если это задание бэкапа, сбора статистики или обслуживания, перенесите его на время вне часов пик.","Добавьте каталоги данных в список исключений антивируса/процессов сканирования.","Если конфликт постоянный, перенесите БД на отдельный сервер с собственными ресурсами."],"related":["os-resource"],"playbook":"high-cpu"}}},"solver/memory-pressure.json":{"id":"memory-pressure","title":"Давление на память / OOM","icon":"🧠","category":"Производительность","intro":"Память сервера заканчивается, система свопится или активен OOM killer. Сначала определите, кто потребляет память: выделенная память (buffer pool/SGA), память сессий (work_mem/PGA), ОС в целом или ситуация, специфичная для Redis?","start":"root","nodes":{"root":{"kind":"step","title":"Кто потребляет память?","detail":"Разделите источник давления на память по уровням. Слишком велика выделенная (фиксированная) память, взрывается память на сессию, её съедает процесс вне БД или дело в собственном управлении памятью Redis?","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":"Преобладает выделенная память (buffer pool / SGA)","next":"allocated"},{"label":"Взрывается память сессий (work_mem / PGA)","next":"sol_session"},{"label":"Процесс вне БД / ОС в целом","next":"sol_os"},{"label":"Специфично для Redis (maxmemory / eviction / фрагментация)","next":"sol_redis"}]},"allocated":{"kind":"step","title":"Превышает ли суммарная настроенная память физическую RAM?","detail":"Если фиксированная выделенная память велика, возможны два варианта: либо настроенная сумма (buffer pool + сессии + ОС) превышает физическую RAM (over-commit), либо размер разумен, но коэффициент попаданий в кэш всё равно низкий. Сложите настройки и сравните с 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":"Да, настроенная сумма превышает RAM (over-commit)","next":"sol_overcommit"},{"label":"Нет, размер разумен, но коэффициент попаданий в кэш низкий","next":"sol_cache"}]},"sol_overcommit":{"kind":"solution","title":"Первопричина: over-commit памяти","detail":"Сумма buffer pool/SGA + памяти сессий + потребностей ОС превышает физическую RAM. Система свопится, или OOM killer завершает процесс.","steps":["Пересчитайте бюджет памяти: зарезервируйте долю для ОС, затем правильно задайте размер выделенной памяти (Инструменты > Калькулятор памяти).","Снизьте целевой размер buffer pool/SGA и потолок памяти сессий, чтобы они укладывались в лимит RAM.","Применяйте изменения контролируемо и убедитесь, что использование swap и события OOM прекратились.","Если ёмкости постоянно не хватает, добавьте RAM или разделите нагрузку."],"related":["show-config-param","cache-hit-ratio"],"playbook":"memory-pressure"},"sol_cache":{"kind":"solution","title":"Первопричина: недостаточный кэш (низкий коэффициент попаданий)","detail":"Выделенная память не превышает RAM, но мала относительно рабочего набора; коэффициент попаданий в буферный кэш низкий, и БД постоянно читает с диска.","steps":["Измерьте hit ratio буферного кэша / buffer pool; если он стабильно низкий (например, ниже 90%), кэш слишком мал.","Если RAM есть, контролируемо увеличьте buffer pool/SGA и снова измерьте hit ratio.","Если горячие данные действительно велики, сократите лишние чтения: добавьте индексы, выбирайте только нужные столбцы вместо SELECT *, используйте постраничный вывод.","Если увеличение нагружает RAM, перераспределите баланс между памятью сессий и выделенной памятью."],"related":["cache-hit-ratio","show-config-param"],"playbook":"memory-pressure"},"sol_session":{"kind":"solution","title":"Первопричина: взрыв памяти сессий","detail":"Память на сессию, такая как work_mem/PGA, умноженная на высокую конкурентность, исчерпывает RAM. Каждая операция sort/hash может выделять эту память отдельно.","steps":["Оцените худший случай: память сессии × одновременные сессии × число операций; сравните с RAM.","Снизьте глобальную настройку памяти сессий (work_mem / sort_buffer_size / PGA target).","Для тяжёлых запросов, которым действительно нужны большие sort/hash, повышайте настройку только на уровне сессии.","Ограничьте параллелизм с помощью пула соединений, чтобы снизить общую потребность в памяти."],"related":["show-config-param","connection-count"],"playbook":"memory-pressure"},"sol_redis":{"kind":"solution","title":"Первопричина: управление памятью Redis","detail":"В Redis used_memory достиг maxmemory, активно вытеснение (eviction) или высокая фрагментация (used_memory_rss >> used_memory). Необходимо разобраться с политикой и фрагментацией.","steps":["Получите used_memory, maxmemory и mem_fragmentation_ratio командой INFO memory.","Задайте политику вытеснения, соответствующую назначению: CONFIG SET maxmemory-policy allkeys-lru (кэш) или noeviction (постоянные данные).","Используйте TTL, чтобы истёкшие ключи удалялись, и уменьшайте большие ключи (BIG KEYS).","При высокой фрагментации включите active defrag (CONFIG SET activedefrag yes) или выполните перезапуск в подходящее окно обслуживания."],"related":["show-config-param"],"playbook":"memory-pressure"},"sol_os":{"kind":"solution","title":"Первопричина: потребление памяти вне БД","detail":"Память потребляет не база данных, а ОС в целом или соседний процесс: другое приложение, давление файлового кэша или вспомогательный процесс с утечкой памяти.","steps":["Определите процесс, потребляющий больше всего памяти, средствами ОС (top / free / Task Manager).","Если виновник — процесс вне БД, перенесите его, ограничьте (cgroup / job object) или устраните утечку памяти.","Если OOM killer завершает процесс БД, пересмотрите настройки OOM и гарантируйте память для БД.","Если конфликт постоянный, перенесите БД на отдельный сервер с собственной памятью."],"related":["os-resource"],"playbook":"memory-pressure"}}},"solver/install-setup.json":{"id":"install-setup","title":"Новая установка и безопасная базовая конфигурация","icon":"🛠️","category":"Жизненный цикл","intro":"Шаги, пропущенные при подготовке новой БД к продуктиву, приводят к самым дорогим сбоям. Выберите текущий этап и получите упорядоченный чек-лист по начальной настройке, усилению безопасности, надёжности и мониторингу.","start":"root","nodes":{"root":{"kind":"step","title":"На каком вы этапе?","detail":"Подготовка новой установки к продуктиву включает четыре этапа: начальная настройка, усиление безопасности, надёжность (бэкап/репликация) и мониторинг. Над каким из них вы сейчас работаете? Рекомендуется пройти все по порядку.","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":"Начальная настройка после установки","next":"firstconfig"},{"label":"Усиление безопасности","next":"sol_security"},{"label":"Настройка бэкапа и надёжности","next":"durability"},{"label":"Настройка мониторинга и оповещений","next":"sol_monitoring"}]},"firstconfig":{"kind":"step","title":"Что приоритетно в начальной настройке?","detail":"Начальная настройка делится на две основные темы: определение ресурсов/параметров (память, соединения) и постоянные основы (кодировка/правила сортировки, каталог данных/размещение файлов). Эти решения труднее всего изменить позже.","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":"Определение размера памяти / параметров","next":"sol_sizing"},{"label":"Кодировка, правила сортировки и размещение файлов","next":"sol_layout"}]},"durability":{"kind":"step","title":"Какую часть надёжности вы настраиваете?","detail":"Надёжность стоит на двух опорах: проверенная стратегия бэкапа (первый бэкап + тест восстановления) и непрерывность (режим архивирования/журналирования, репликация). Говорить «у меня есть бэкап», не проверив его, — самое распространённое заблуждение.","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":"Стратегия бэкапа + первый бэкап + тест восстановления","next":"sol_backup"},{"label":"Режим архивирования/журналирования + репликация","next":"sol_replication"}]},"sol_sizing":{"kind":"solution","title":"Начальная настройка: определение размера памяти и параметров","detail":"Значения по умолчанию в большинстве СУБД консервативны; подберите их под RAM и CPU сервера, но избегайте over-commit.","steps":["Распределите общий объём RAM: оставьте долю для ОС, затем задайте buffer pool/SGA как основной кэш.","Задайте память сессий (work_mem/PGA) консервативно с учётом ожидаемого параллелизма; не превышайте общий объём RAM.","Установите max_connections по фактической потребности; при высоком параллелизме запланируйте пул соединений.","Проверьте ключевые параметры конкретной СУБД (WAL/redo, checkpoint, параллелизм) и задокументируйте изменения."],"related":["show-config-param","connect-cli"]},"sol_layout":{"kind":"solution","title":"Начальная настройка: кодировка, правила сортировки и размещение файлов","detail":"Кодировка/правила сортировки и структура каталога данных задаются при установке; их последующее изменение обычно требует переустановки или миграции.","steps":["Выберите кодировку Unicode на уровне сервера (например, UTF-8 / AL32UTF8) и задайте согласованные правила сортировки (collation).","Разместите каталоги данных, журналов/WAL и бэкапов на отдельных дисках/томах; снизьте конкуренцию за IO и устраните единые точки отказа.","Усильте защиту файловой системы и прав доступа: читать каталог данных должен только пользователь БД.","Задокументируйте решения о размещении и кодировке, чтобы добавляемые позже узлы им соответствовали."],"related":["show-config-param","connect-cli"]},"sol_security":{"kind":"solution","title":"Усиление безопасности","detail":"Новая установка поставляется с учётными записями по умолчанию, свободным сетевым доступом и открытыми портами. Сокращение поверхности атаки до вывода в продуктив обязательно.","steps":["Удалите или заблокируйте учётные записи по умолчанию/демонстрационные; отключите неиспользуемые административные учётные записи.","Введите строгую парольную политику и смените все пароли по умолчанию.","Минимальные привилегии: выдавайте каждому приложению только необходимые привилегии и избегайте широких ролей.","Включите TLS, ограничьте listener/порт доверенными сетями и включите журналирование аудита."],"related":["create-user-grant","change-password","enable-audit"]},"sol_backup":{"kind":"solution","title":"Надёжность: стратегия бэкапа и тест восстановления","detail":"Бэкап хорош ровно настолько, насколько его можно восстановить. Сделайте первый бэкап, обеспечьте непрерывность и обязательно выполните тест восстановления.","steps":["Определите цели восстановления (RPO/RTO) и настройте расписание полных + инкрементальных бэкапов.","Сделайте первый полный бэкап; храните бэкапы отдельно от сервера БД.","Проверьте бэкап, восстановив его в отдельной среде; бэкап, целостность которого не доказана, не считается.","Запланируйте задание бэкапа, передавайте его результат в мониторинг и регулярно проверяйте статус последнего бэкапа."],"related":["logical-dump","restore-dump","last-backup-status"]},"sol_replication":{"kind":"solution","title":"Надёжность: режим архивирования/журналирования и репликация","detail":"Для восстановления на момент времени и высокой доступности должен быть включён режим архивирования/журналирования и должна быть хотя бы одна реплика.","steps":["Включите режим архивирования/журналирования (PG archive_mode/wal_level, Oracle ARCHIVELOG, MySQL binlog, MSSQL FULL recovery).","Настройте хотя бы одну реплику (standby/replica) и завершите начальную синхронизацию.","Отслеживайте задержку репликации и задайте оповещение о задержке.","Проверьте отработку отказа, проведя контролируемые учения failover/switchover."],"related":["replication-status","list-replicas"],"playbook":"replication-lag"},"sol_monitoring":{"kind":"solution","title":"Настройка мониторинга и оповещений","detail":"Последний шаг перед продуктивом: видеть проблемы раньше пользователей. Настройте проверки состояния и оповещения с правильными порогами.","steps":["Соберите базовые проверки состояния: коэффициент попаданий в кэш, активные/ожидающие сессии, самые медленные запросы.","Задайте оповещения о ёмкости: использование диска, приближение числа соединений к лимиту, задержка репликации.","Направляйте оповещения в канал (email/чат) и откалибруйте пороги по реальной нагрузке.","Также добавьте в мониторинг успешность бэкапов и журналы ошибок, чтобы ловить скрытые сбои."],"related":["cache-hit-ratio","connection-count","free-space"]}}},"solver/corruption-recovery.json":{"id":"corruption-recovery","title":"Восстановление после повреждения данных","icon":"🧯","category":"Восстановление","intro":"Пошаговое дерево решений при повреждении данных (ошибки контрольных сумм блоков/страниц, нечитаемые таблицы, повреждённые RDB/AOF): сначала подтвердите масштаб повреждения, затем правильно выберите между восстановлением на уровне блоков, полным восстановлением/PITR из бэкапа и инструментами исправления как крайней мерой.","start":"root","nodes":{"root":{"kind":"step","title":"Каков масштаб повреждения?","detail":"Сначала выполните проверку целостности, чтобы определить, насколько распространилось повреждение. Повреждены лишь несколько блоков/страниц, затронут один объект (таблица/индекс) или повреждение массовое? Просканируйте командами ниже, чтобы определить масштаб, затем выберите подходящую ветку.","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":"Повреждены лишь несколько блоков / страниц","next":"block_level"},{"label":"Затронут один объект (таблица/индекс)","next":"restore"},{"label":"Повреждение массовое / много объектов","next":"restore"},{"label":"Средства проверки не работают, БД не открывается","next":"last_resort"},{"label":"Повреждение повторяется (подозревается первопричина)","next":"hardware"}]},"block_level":{"kind":"solution","title":"Первопричина: ограниченное повреждение блоков/страниц → точечное восстановление","detail":"Если повреждены лишь несколько блоков/страниц, восстанавливать всю базу данных не нужно. Сведите потерю данных к минимуму, восстановив из бэкапа/журналов только повреждённые части функцией восстановления на уровне блоков/страниц.","steps":["Oracle: изучив список повреждённых блоков в v$database_block_corruption (если список устарел, сначала выполните RMAN> VALIDATE DATABASE;), восстановите отдельный блок командой RMAN> RECOVER DATAFILE 5 BLOCK 1234; или все блоки из списка командой RMAN> RECOVER CORRUPTION LIST;.","SQL Server: восстановление отдельной страницы — RESTORE DATABASE [db] PAGE='5:1234' FROM DISK='D:\\backup\\db.bak' WITH NORECOVERY;  затем примените последующие бэкапы журнала WITH NORECOVERY, а последний — WITH RECOVERY.","PostgreSQL: автоматического исправления на уровне блоков нет; частично восстановите затронутую таблицу из бэкапа или временно включите zero_damaged_pages=on ТОЛЬКО для извлечения данных (повреждённые строки теряются).","MySQL/InnoDB: исправления отдельной страницы нет; восстановите затронутую таблицу из последнего исправного бэкапа и при необходимости докатите с помощью PITR.","После исправления повторно выполните команду проверки (VALIDATE / DBCC CHECKDB / pg_amcheck), чтобы убедиться, что повреждений не осталось.","Проверьте бизнес-данные в восстановленных блоках выборочно на стороне приложения."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"},"restore":{"kind":"solution","title":"Первопричина: повреждение на уровне объектов/массовое → полное восстановление или PITR из бэкапа","detail":"Если повреждение распространилось на один или несколько объектов, самый безопасный путь — восстановление из исправного бэкапа и, по возможности, докат до момента повреждения (PITR). Так вы избежите потери данных, которую вызвали бы инструменты исправления.","steps":["Определите примерное время начала повреждения (alert log / error log), чтобы задать цель PITR непосредственно перед ним.","Проверьте последний ИСПРАВНЫЙ полный бэкап и журналы транзакций до этого момента (архивные журналы / 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: восстановите базовый бэкап, задайте recovery_target_time='2026-07-04 09:59:00' и примените WAL до этого момента.","MySQL/MariaDB: восстановите полный бэкап, затем примените binlog до этого момента с помощью mysqlbinlog --stop-datetime='2026-07-04 09:59:00'.","MongoDB: восстановите с помощью mongorestore из исправной реплики/снимка; Elasticsearch: восстановите повреждённые индексы через POST _snapshot/my_repo/snap1/_restore.","После открытия повторно выполните проверку целостности и проверьте данные по числу строк/проверками приложения."],"related":["restore-dump","logical-dump","last-backup-status"],"playbook":"corruption"},"last_resort":{"kind":"step","title":"Бэкапа нет / БД не открывается — переходить к крайней мере?","detail":"Инструменты исправления (REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery, --fix) МОГУТ ПРИВЕСТИ К ПОТЕРЕ ДАННЫХ и нарушить ограничения целостности. Это крайняя мера, только когда нет пригодного бэкапа и базу данных нельзя открыть. Сначала убедитесь, что пригодного бэкапа действительно нет.","choices":[{"label":"Я нашёл исправный бэкап, восстанавливаю","next":"restore"},{"label":"Бэкапа нет совсем, единственный вариант — инструмент исправления","next":"repair"}]},"repair":{"kind":"solution","title":"Крайняя мера: инструменты исправления с риском потери данных","detail":"Эти инструменты отбрасывают повреждённые строки/страницы, чтобы базу данных можно было открыть. Используйте их, только если бэкапа нет и вы предварительно сделали копию существующих файлов. Цель: открыть ровно настолько, чтобы выгрузить данные, затем перенести их в чистую копию.","steps":["СНАЧАЛА сделайте сырую копию существующих файлов данных (попытки могут ухудшить ситуацию).","SQL Server: DBCC CHECKDB (N'db', REPAIR_ALLOW_DATA_LOSS) WITH ALL_ERRORMSGS;  (база данных должна быть в режиме SINGLE_USER; эта команда может привести к потере данных).","MySQL/InnoDB: запустите с innodb_force_recovery=1 в секции [mysqld] файла my.cnf; если не открывается, увеличивайте до 2, 3... (до 6); как только откроется, сразу выгрузите данные с помощью mysqldump, затем импортируйте их в чистую установку и удалите параметр.","PostgreSQL: как крайняя мера — pg_resetwal (высокий риск потери данных) только при повреждении WAL; затем извлеките данные с помощью pg_dump и загрузите их в новый кластер.","Oracle: без бэкапа DBMS_REPAIR / восстановление на основе событий оценивается совместно с Oracle Support; блоки можно пропустить с помощью 'skip corrupt' (потеря данных).","MongoDB: откройте с помощью mongod --repair или WiredTiger salvage, затем перенесите данные в новый replica set.","После исправления ВСЕГДА переходите на чистую установку, повторно выполняйте проверку целостности и сообщайте о потерянных данных."],"related":["logical-dump","restore-dump"],"playbook":"corruption"},"hardware":{"kind":"solution","title":"Первопричина: оборудование/инфраструктура → устраните источник и включите контрольные суммы","detail":"Если повреждение повторяется, реальная причина скорее всего в диске, контроллере, памяти (RAM) или нестабильном уровне хранения. Одного восстановления данных недостаточно: если не устранить источник, повреждение вернётся. Также включите проверку контрольных сумм/целостности для раннего обнаружения.","steps":["Проверьте состояние дисков: значения SMART (smartctl -a /dev/sdX), журналы RAID-контроллера и оповещения системы хранения.","Выполните тест памяти (memtest86+ или счётчики ошибок ECC); однобитовые ошибки — классическая причина повреждений.","Изучите журналы ошибок ОС/файловой системы и оборудования (dmesg, Event Viewer, оповещения массива хранения).","PostgreSQL: создайте кластер с контрольными суммами с помощью initdb --data-checksums (или pg_checksums --enable), чтобы повреждение обнаруживалось при записи на диск.","Oracle: включите проверку блоков с помощью DB_BLOCK_CHECKSUM=FULL и DB_BLOCK_CHECKING=FULL; добавьте DB_LOST_WRITE_PROTECT=TYPICAL.","SQL Server: убедитесь, что база данных использует PAGE_VERIFY CHECKSUM (ALTER DATABASE [db] SET PAGE_VERIFY CHECKSUM).","MySQL/InnoDB: защититесь от частичной записи с помощью innodb_checksum_algorithm=crc32 и при необходимости innodb_doublewrite=ON.","Устранив источник, восстановите последний исправный бэкап в чистую копию и усильте мониторинг."],"related":["restore-dump","last-backup-status"],"playbook":"corruption"}}},"solver/failover-drill.json":{"id":"failover-drill","title":"Учения по отработке отказа / плановая смена ролей","icon":"🔀","category":"Репликация","intro":"Дерево решений, которое определяет тип смены ролей (плановое переключение, аварийная отработка отказа или учения без затрагивания продуктива) и даёт правильную последовательность команд и шаги проверки для каждого сценария. Цель: быть готовым к переходу без потери данных в плановом случае, с минимальными потерями в аварийном и вообще без простоя на учениях.","start":"root","nodes":{"root":{"kind":"step","title":"Что нужно сделать?","detail":"Выберите контекст смены ролей. Вы выполняете плановый переход при исправном первичном сервере (обслуживание/миграция), первичный сервер действительно вышел из строя и нужен аварийный переход, или вы проводите учения/проверку готовности, не затрагивая продуктив?","choices":[{"label":"Плановое переключение (switchover, первичный сервер исправен)","next":"planned"},{"label":"Аварийная отработка отказа (failover, первичный сервер недоступен)","next":"emergency_check"},{"label":"Учения / тест готовности (не затрагивать продуктив)","next":"drill"}]},"planned":{"kind":"step","title":"Плановый переход — вы готовы?","detail":"Цель планового переключения — НУЛЕВАЯ потеря данных. Для этого перед переходом нужно убедиться, что реплика догнала первичный сервер (задержка ~0) и исправна. Проверьте задержку командами ниже; если она близка к нулю, переходите к шагам перехода.","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":"Задержка близка к нулю, готов к переходу","next":"sol_planned"},{"label":"Задержка высокая, сначала дождитесь синхронизации","next":"drill"}]},"sol_planned":{"kind":"solution","title":"Плановое переключение (контролируемый переход без потери данных)","detail":"Пока первичный и резервный серверы исправны и синхронизированы, поменяйте роли контролируемо. Порядок важен: остановите запись, убедитесь, что оставшиеся изменения дошли до standby, затем передайте роль и перенаправьте приложение на новый первичный сервер.","steps":["Объявите окно обслуживания и контролируемо приостановите чтение и запись приложением.","Oracle Data Guard (12c+): SQL> ALTER DATABASE SWITCHOVER TO standby_db VERIFY; затем SQL> ALTER DATABASE SWITCHOVER TO standby_db;  (через Broker: DGMGRL> SWITCHOVER TO 'standby_db'; 11g и ранее: COMMIT TO SWITCHOVER TO PHYSICAL STANDBY).","PostgreSQL: корректно остановите первичный сервер (pg_ctl stop -m fast), повысьте standby командой SELECT pg_promote();, затем снова подключите старый первичный сервер как новый standby с помощью pg_rewind.","SQL Server AG: на синхронной реплике ALTER AVAILABILITY GROUP [ag] FAILOVER;  (плановая отработка отказа без потери данных; требуется SYNCHRONOUS_COMMIT).","MongoDB: с помощью rs.stepDown(60) заставьте текущий primary сложить полномочия на 60 с; наиболее актуальный secondary автоматически избирается и становится primary.","Redis: на кандидате в новый master выполните REPLICAOF NO ONE; затем на остальных узлах REPLICAOF new_master port; (с Sentinel — SENTINEL FAILOVER master_name).","Направьте строку подключения / маршрутизацию приложения на новый первичный сервер (VIP, имя сервиса или строка подключения).","Проверьте: новый первичный сервер принимает запись, старый первичный работает как standby в режиме потоковой репликации с задержкой ~0, у приложения нет ошибок."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"replication-lag"},"emergency_check":{"kind":"step","title":"Аварийный переход — первичный сервер действительно недоступен?","detail":"Перед принудительной отработкой отказа убедитесь, что первичный сервер действительно недоступен, и оцените риск split-brain. Если первичный сервер на самом деле жив (например, кратковременный сбой сети), принудительный переход приведёт к двум отдельным копиям, доступным для записи (split-brain). Также сравните задержку, чтобы выбрать наиболее актуальный 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":"Да, первичный сервер недоступен — выполнить принудительный переход","next":"emergency"},{"label":"Не уверен / первичный сервер может вернуться","next":"planned"}]},"emergency":{"kind":"solution","title":"Аварийная отработка отказа (первичный сервер недоступен, переход с риском потери данных)","detail":"Первичный сервер недоступен, и ждать его возвращения нельзя. Нужно принудительно повысить standby; при асинхронной репликации часть последних транзакций может быть потеряна. Приоритет — восстановить сервис, затем определить окно потерь и безопасно вернуть старый первичный сервер.","steps":["Убедитесь, что первичный сервер действительно недоступен (изолируйте старый первичный сервер от сети / остановите его, чтобы предотвратить split-brain — fencing/STONITH).","Выберите наиболее актуальный standby: перед действиями сравните задержку/смещение, чтобы повысить тот, у которого потеря данных минимальна.","Oracle Data Guard: DGMGRL> FAILOVER TO 'standby_db';  (или SQL> ALTER DATABASE ACTIVATE STANDBY DATABASE;).","PostgreSQL: немедленно повысьте standby командой SELECT pg_promote();; когда старый первичный сервер вернётся, НЕ ОТКРЫВАЙТЕ его как первичный без pg_rewind (риск split-brain).","SQL Server AG: ALTER AVAILABILITY GROUP [ag] FORCE_FAILOVER_ALLOW_DATA_LOSS;  (при асинхронной реплике возможна потеря данных).","MongoDB: если большинства не осталось, переконфигурируйте уцелевших участников с помощью rs.reconfig(cfg, {force:true}).","Redis: без Sentinel выполните REPLICAOF NO ONE на кандидате в новый master; подключите к нему остальные; никогда не позволяйте старому master автоматически вернуться в состав.","Перенаправьте приложение на новый первичный сервер; затем определите и задокументируйте окно потерянных данных (потерянные транзакции) и корректно пересоздайте старый первичный сервер как новый standby."],"related":["promote-failover","replication-status","list-replicas"],"playbook":"db-down"},"drill":{"kind":"solution","title":"Учения / тест готовности (проверка без затрагивания продуктива)","detail":"Докажите готовность к отработке отказа без реального перехода. Проверьте состояние реплик, допустимость задержки и возможность перенаправить приложение, не затрагивая продуктив. При желании отрепетируйте реальное переключение на изолированной копии.","steps":["Измерьте задержку и убедитесь, что она ниже порога (PG: pg_stat_replication.replay_lag; MySQL: Seconds_Behind_Source; Oracle: v$dataguard_stats; Mongo: rs.printSecondaryReplicationInfo()).","Проверьте состояние реплик: все standby получают поток, синхронный режим активен на ожидаемых участниках (SQL Server: synchronization_state_desc='SYNCHRONIZED').","Тест чтения/роли: выполните запрос только на чтение на standby и убедитесь, что ответ приходит без задержки (PG hot standby, Oracle Active Data Guard, read replica).","Репетиция маршрутизации приложения: в тестовой среде направьте строку подключения/имя сервиса на новый первичный сервер и убедитесь, что приложение подключается без проблем (продуктивный трафик не затрагивайте).","Отрепетируйте реальное переключение от начала до конца на изолированной копии и измерьте время перехода (RTO).","Отрепетируйте также шаги обратного переключения (failback): пересоздание старого первичного сервера с помощью pg_rewind / DG reinstate / AG resume.","Зафиксируйте выводы, время и пробелы; обновите runbook и проводите учения периодически."],"related":["replication-status","list-replicas","promote-failover"],"playbook":"replication-lag"}}},"solver/security-incident.json":{"id":"security-incident","title":"Инцидент безопасности / подозрительный доступ","icon":"🚨","category":"Безопасность","intro":"Дерево решений для подозрительного доступа, неожиданного изменения привилегий или возможной утечки данных: в зависимости от симптома ведёт к правильным шагам реагирования (блокировка учётной записи, отзыв лишних привилегий, разрыв соединений, определение масштаба) и к постоянному усилению защиты после инцидента.","start":"root","nodes":{"root":{"kind":"step","title":"Что вы заметили?","detail":"Выберите тип инцидента. Симптом — подозрительный вход или всплеск неудачных входов, неожиданное изменение привилегий (GRANT/роль), возможная утечка данных / аномальная выгрузка, или инцидент закрыт и вы выполняете постоянное усиление защиты? Выберите наиболее подходящую ветку по первому наблюдению. Сначала зафиксируйте время инцидента и затронутую учётную запись/ресурс.","choices":[{"label":"Подозрительный вход / всплеск неудачных входов","next":"identity"},{"label":"Неожиданное изменение привилегий (GRANT/роль)","next":"privilege"},{"label":"Утечка данных / аномальная выгрузка","next":"data"},{"label":"Постоянные меры после инцидента (усиление защиты)","next":"harden"}]},"identity":{"kind":"step","title":"Инцидент с учётными данными: какой симптом?","detail":"Множество неудачных входов указывает на атаку перебором (brute force) / credential stuffing. Напротив, успешный, но неожиданный вход (с необычного IP / в необычное время) говорит о возможной компрометации учётной записи. В обоих случаях сначала соберите доказательства, затем реагируйте.","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":"Всплеск неудачных входов (brute force)","next":"id_bruteforce"},{"label":"Успешный, но неожиданный вход (компрометация)","next":"id_compromised"}]},"id_bruteforce":{"kind":"solution","title":"Brute force → заблокируйте источник и защитите учётную запись","detail":"При всплеске неудачных входов доступ, возможно, ещё не получен; цель — заблокировать источник, защитить учётную запись блокировкой/задержкой и исключить успешное проникновение.","steps":["Определите атакующие IP (запросы выше) и заблокируйте их на уровне сети: firewall / pg_hba.conf reject / listener ACL / security group.","Временно заблокируйте атакуемую учётную запись: 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.","Примените ограничение попыток входа / политику блокировки: Oracle: FAILED_LOGIN_ATTEMPTS в профиле; PostgreSQL: модуль auth_delay (shared_preload_libraries; он задерживает неудачные попытки, а не блокирует); MySQL: FAILED_LOGIN_ATTEMPTS ... PASSWORD_LOCK_TIME.","В любом случае смените пароль атакуемой учётной записи на надёжный (одна из попыток могла быть успешной).","Проверьте, была ли попытка успешной: поищите успешный вход с того же IP; если он есть, пройдите также ветку компрометации.","Задокументируйте инцидент с хронологией и выполните шаг усиления защиты для постоянных мер («Постоянные меры после инцидента» из корня)."],"related":["change-password","list-users"],"playbook":"harden-sql"},"id_compromised":{"kind":"solution","title":"Скомпрометированная учётная запись → остановить, расследовать, сменить учётные данные","detail":"Неожиданный успешный вход говорит о компрометации учётной записи. Сначала остановите учётную запись и её сессии, выясните, что она делала, затем смените пароль и сузьте источник доступа.","steps":["Немедленно заблокируйте учётную запись (команды LOCK/DISABLE выше) и завершите её активные сессии: 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.","Выясните, что учётная запись делала в своих сессиях: выполненные запросы, таблицы, к которым обращалась, и любые операции GRANT/CREATE в журнале аудита/логах.","Смените пароль на надёжный и смените все связанные учётные данные сервисов/приложений.","Поищите новые учётные записи или изменения привилегий, которые мог создать злоумышленник (пройдите также ветку привилегий).","Сузьте источник доступа: разрешите учётной записи подключаться только с известных IP (pg_hba.conf / listener ACL / firewall).","Выполните шаг усиления защиты для постоянных мер («Постоянные меры после инцидента» из корня)."],"related":["change-password","list-users","show-privileges"],"playbook":"harden-sql"},"privilege":{"kind":"solution","title":"Повышение привилегий → аудит и отзыв лишних привилегий","detail":"Неожиданный GRANT, назначение роли или новая учётная запись с высокими привилегиями могут быть второй стадией компрометации (закрепление/повышение привилегий). Проверьте последние изменения привилегий, отзовите нелегитимные и вернитесь к принципу минимальных привилегий.","steps":["Проверьте последние изменения привилегий: Oracle: SELECT * FROM dba_audit_trail WHERE action_name IN ('GRANT','REVOKE','CREATE USER') ORDER BY timestamp DESC;  SQL Server: события GRANT в default trace / Server Audit и SELECT * FROM sys.server_permissions;  MySQL: записи GRANT в журнале аудита и SELECT * FROM mysql.user;  MongoDB: db.getRoles({showPrivileges:true}).","Выведите текущие привилегии: 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'@'%';","Отзовите нелегитимные лишние привилегии: 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'@'%';","Найдите и удалите скрытые/резервные учётные записи, которые мог создать злоумышленник (неожиданные записи CREATE USER).","Проверьте и сузьте избыточные привилегии, выданные PUBLIC или широким ролям.","Убедитесь, что изменения соответствуют легитимному запросу на изменение; всё несоответствующее считайте частью инцидента и выполните шаг усиления защиты."],"related":["show-privileges","list-users","create-user-grant"],"playbook":"harden-sql"},"data":{"kind":"solution","title":"Утечка данных → разорвать доступ и определить масштаб","detail":"Аномально большие запросы/выгрузки или неожиданные внешние соединения указывают на утечку данных. Сначала разорвите активные опасные соединения, затем определите масштаб по журналу аудита/логам: к каким данным, кто и когда обращался.","steps":["Проверьте источники активных соединений: 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.","Немедленно разорвите подозрительные/внешние соединения и заблокируйте эту учётную запись (команды KILL/LOCK).","Заблокируйте источник на уровне сети: закройте подозрительный блок IP с помощью firewall / pg_hba.conf / listener ACL.","Изучите журнал аудита: определите, какие таблицы читались (SELECT) или выгружались (EXPORT), число строк и временное окно (Oracle unified_audit_trail; SQL Server sys.fn_get_audit_file; журналы PostgreSQL pgaudit; MongoDB auditLog).","Ищите следы выгрузки: большие результирующие наборы, COPY / SELECT ... INTO OUTFILE, mongoexport, паттерны массового чтения.","Задокументируйте масштаб затронутых данных (какие таблицы, какие персональные/конфиденциальные поля) и возможный объём; оцените юридические обязательства/обязанность уведомления и выполните шаг усиления защиты."],"related":["show-privileges","list-users"],"playbook":"harden-sql"},"harden":{"kind":"solution","title":"Постоянное усиление защиты после инцидента","detail":"После экстренного реагирования примените постоянные меры контроля, чтобы инцидент не повторился: включите аудит, обеспечьте минимальные привилегии, ограничьте сетевой доступ и полностью задокументируйте инцидент.","steps":["Включите аудит: фиксируйте критичные события (входы, GRANT/REVOKE, доступ к чувствительным таблицам) в журнале аудита; подробности — в дереве «Настройка аудита».","Минимальные привилегии: выдавайте каждой учётной записи только необходимые привилегии, разделите общие/сервисные учётные записи, отключите неиспользуемые.","Политика паролей/идентификации: надёжные пароли, регулярная смена, многофакторная аутентификация где возможно и централизованная идентификация (LDAP/Kerberos).","Сетевые ограничения: открывайте базу данных только для серверов приложений (firewall, security group, pg_hba.conf, listener ACL); заблокируйте прямой доступ из интернета.","Шифрование: включите TLS при передаче и, где требуется, шифрование хранимых данных.","Мониторинг/оповещения: настройте оповещения о всплесках неудачных входов, новых GRANT и аномальных объёмах данных.","Запись об инциденте: задокументируйте хронологию, затронутые активы, предпринятые действия и извлечённые уроки; обновите runbook."],"related":["list-users","show-privileges","change-password"]}}},"solver/audit-setup.json":{"id":"audit-setup","title":"Настройка аудита","icon":"🧾","category":"Безопасность","intro":"Дерево решений, которое в зависимости от объекта аудита (входы/сессии, DDL/изменения схемы, доступ к данным или изменения привилегий) включает аудит этой области в нужной СУБД, а затем даёт шаги по чтению записей и их хранению/ротации.","start":"root","nodes":{"root":{"kind":"step","title":"Что вы хотите аудировать?","detail":"Выберите область аудита. Аудит всего подряд создаёт шум и затраты; сузьте его до своих потребностей. Вы хотите отслеживать события входа/сессий, DDL/изменения схемы, доступ к данным (DML/SELECT) или изменения привилегий (GRANT/REVOKE)?","choices":[{"label":"Входы / сессии (login, connect)","next":"login"},{"label":"DDL / изменения схемы (CREATE/ALTER/DROP)","next":"ddl"},{"label":"Доступ к данным (DML / SELECT)","next":"dml"},{"label":"Изменения привилегий (GRANT / REVOKE)","next":"grants"},{"label":"Чтение записей аудита и хранение / ротация","next":"retention"}]},"login":{"kind":"solution","title":"Аудит входов / сессий","detail":"Фиксирует успешные и неудачные входы: кто подключился, откуда и когда. Это основа обнаружения несанкционированного доступа и попыток перебора. Включите соответствующую политику для каждой СУБД, затем читайте и храните записи.","steps":["Oracle (Unified Auditing): CREATE AUDIT POLICY login_pol ACTIONS LOGON, LOGOFF;  AUDIT POLICY login_pol;  (для неудачных: AUDIT POLICY login_pol WHENEVER NOT SUCCESSFUL;).","PostgreSQL: задайте log_connections=on и log_disconnections=on в postgresql.conf и перечитайте конфигурацию; соединения журналируются без pgaudit.","MySQL/MariaDB: установите плагин audit_log (INSTALL PLUGIN audit_log ...) и выполните SET GLOBAL audit_log_policy='LOGINS'; (MariaDB: server_audit_events='CONNECT').","SQL Server: создайте Server Audit (CREATE SERVER AUDIT ...), добавьте FAILED_LOGIN_GROUP и SUCCESSFUL_LOGIN_GROUP в Server Audit Specification и выполните ENABLE для обоих.","MongoDB: запустите с --auditDestination file --auditFilter '{ atype: { $in: [\"authenticate\", \"logout\"] } }' (Enterprise/Percona).","Redis: отслеживайте неудачные попытки аутентификации/команд с помощью ACL LOG (для подробностей используйте ACL log, а не 'notify').","Elasticsearch: задайте xpack.security.audit.enabled: true в elasticsearch.yml и добавьте authentication_success, authentication_failed в список include.","Для чтения и хранения/ротации записей выполните шаг «Чтение записей аудита и хранение / ротация» из корня."],"related":["list-users","change-password"]},"ddl":{"kind":"solution","title":"Аудит DDL / изменений схемы","detail":"Фиксирует изменения схемы, такие как CREATE/ALTER/DROP. Используется для обнаружения несанкционированных изменений объектов, скрытых таблиц/бэкдоров и дрейфа схемы.","steps":["Oracle: CREATE AUDIT POLICY ddl_pol ACTIONS CREATE TABLE, ALTER TABLE, DROP TABLE, CREATE USER, ALTER USER;  AUDIT POLICY ddl_pol;","PostgreSQL (pgaudit): загрузите его через shared_preload_libraries='pgaudit', затем ALTER SYSTEM SET pgaudit.log='ddl'; SELECT pg_reload_conf();","MySQL/MariaDB: с audit_log выполните SET GLOBAL audit_log_policy='ALL'; затем отфильтруйте DDL-операторы в журнале (MariaDB: server_audit_events='QUERY_DDL').","SQL Server: добавьте SCHEMA_OBJECT_CHANGE_GROUP (и DATABASE_OBJECT_CHANGE_GROUP) в Database Audit Specification и выполните ENABLE.","MongoDB: --auditFilter '{ atype: { $in: [\"createCollection\",\"dropCollection\",\"createIndex\",\"dropIndex\",\"createDatabase\",\"dropDatabase\"] } }'.","Elasticsearch: добавьте события, связанные с index_creation/index_deletion, в список include аудита; для изменений схемы (mapping) журналируйте вызовы _mapping как запросы.","Для чтения и хранения/ротации записей выполните шаг «Чтение записей аудита и хранение / ротация» из корня."],"related":["show-privileges"]},"dml":{"kind":"solution","title":"Аудит доступа к данным (DML / SELECT)","detail":"Фиксирует доступ на чтение/запись (SELECT/INSERT/UPDATE/DELETE) к чувствительным таблицам. Поскольку объём может быть большим, ограничьте аудит чувствительными объектами. Это критично для обнаружения утечек и соответствия требованиям (KVKK/GDPR).","steps":["Сузьте область: аудируйте только чувствительные таблицы/столбцы, а не все данные (для контроля шума и затрат).","Oracle: CREATE AUDIT POLICY dml_pol ACTIONS SELECT, INSERT, UPDATE, DELETE ON HR.SALARIES;  AUDIT POLICY dml_pol;","PostgreSQL (pgaudit): для широкого журналирования используйте ALTER SYSTEM SET pgaudit.log='read,write'; либо для точечного аудита на уровне объектов выдайте роли аудита привилегии только на нужные таблицы и задайте pgaudit.role.","MySQL/MariaDB: журналируйте только нужные таблицы/пользователей с помощью фильтра audit_log (audit_log_filter / MariaDB server_audit_events='QUERY_DML').","SQL Server: добавьте действия SELECT, INSERT, UPDATE, DELETE для конкретных таблиц и участника (например, public) в Database Audit Specification и выполните ENABLE.","MongoDB: --auditFilter '{ atype: \"authCheck\", \"param.command\": { $in: [\"find\",\"insert\",\"update\",\"delete\"] }, \"param.ns\": \"db.sensitive\" }' (следите за объёмом).","Elasticsearch: добавьте access_granted/access_denied в список include аудита и используйте emit_request_body, чтобы видеть, на какой индекс направлен запрос (будьте осторожны при журналировании тел запросов с чувствительными данными).","Для чтения и хранения/ротации записей выполните шаг «Чтение записей аудита и хранение / ротация» из корня."],"related":["show-privileges","list-users"]},"grants":{"kind":"solution","title":"Аудит изменений привилегий (GRANT / REVOKE)","detail":"Фиксирует изменения привилегий и ролей (GRANT/REVOKE, назначение ролей, создание пользователей). Это основа обнаружения повышения привилегий и учётных записей-бэкдоров; обычно аудит небольшого объёма, но высокой ценности.","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();  (фиксирует события GRANT/REVOKE/CREATE ROLE).","MySQL/MariaDB: журналируйте операторы GRANT/REVOKE/CREATE USER с помощью audit_log (политика 'ALL' или DDL) и оставляйте ACL-операторы в фильтре.","SQL Server: добавьте SERVER_PERMISSION_CHANGE_GROUP, SERVER_ROLE_MEMBER_CHANGE_GROUP и DATABASE_PERMISSION_CHANGE_GROUP в Server Audit Specification и выполните ENABLE.","MongoDB: --auditFilter '{ atype: { $in: [\"grantRolesToUser\",\"revokeRolesFromUser\",\"createUser\",\"dropUser\",\"createRole\"] } }'.","Elasticsearch: для изменений ролей/пользователей журналируйте в аудите события запросов, связанных с безопасностью (вызовы API realm/role).","Для чтения и хранения/ротации записей выполните шаг «Чтение записей аудита и хранение / ротация» из корня."],"related":["show-privileges","create-user-grant","list-users"]},"retention":{"kind":"solution","title":"Чтение записей аудита и хранение / ротация","detail":"Аудит полезен, только если записи читают и хранят. Определите здесь, как запрашивать записи, где они хранятся, и политику хранения/ротации, удерживающую их рост под контролем.","steps":["Чтение записей: Oracle: SELECT event_timestamp, dbusername, action_name, object_name, sql_text FROM unified_audit_trail ORDER BY event_timestamp DESC;  PostgreSQL: вывод pgaudit пишется в журнал сервера (log_directory); отфильтруйте строки 'AUDIT:' с помощью grep.","SQL Server: SELECT * FROM sys.fn_get_audit_file('D:\\audit\\*.sqlaudit', DEFAULT, DEFAULT);  MySQL: читайте файл audit_log (JSON/XML) или server_audit.log в MariaDB; MongoDB: разбирайте audit.json; Elasticsearch: запрашивайте индексы .security-audit-*.","Задайте срок хранения (в соответствии с требованиями регуляторов, например 90 дней / 1 год) и закрепите его в политике.","Ротация/очистка: Oracle: архивируйте/удаляйте старые записи с помощью DBMS_AUDIT_MGMT.SET_LAST_ARCHIVE_TIMESTAMP и CLEAN_AUDIT_TRAIL; для файловых журналов PostgreSQL/MySQL/MongoDB выполняйте ротацию по размеру/времени с помощью logrotate.","Elasticsearch: задайте политику ILM (rollover + delete) для индексов аудита.","Отправляйте записи аудита в неизменяемое (WORM) хранилище с доступом, отделённым от DBA (разделение обязанностей); ограничьте доступ.","Следите за ростом хранилища: периодически проверяйте, что аудит не заполняет диск и не влияет чрезмерно на производительность."],"related":["list-users","show-privileges"]}}},"solver/decide-ha.json":{"id":"decide-ha","title":"Решение: какое решение HA/DR?","icon":"🧭","category":"Руководство по выбору","intro":"Выбор решения высокой доступности и аварийного восстановления начинается не с технологии, а с баланса между требованиями бизнеса (RTO/RPO), потребностью в географическом распределении и стоимостью. Исходя из допустимых простоя и потери данных, это дерево указывает правильный подход среди синхронных реплик, асинхронных standby, кластеров с автоматической отработкой отказа и бэкапа+PITR.","start":"root","nodes":{"root":{"kind":"step","title":"Какова допустимая потеря данных (RPO)?","detail":"Начните с самого жёсткого ограничения: сколько секунд данных вы можете позволить себе потерять при аварии? Если требуется нулевая потеря данных (RPO=0), синхронная репликация обязательна; если допустимы несколько секунд/минут, становятся доступны асинхронные решения и более дешёвые подходы. Цель RPO обычно определяет все остальные решения.","choices":[{"label":"Нулевая потеря данных обязательна (RPO = 0)","next":"rpo_zero"},{"label":"Потеря данных за несколько секунд/минут допустима","next":"rpo_soft"}]},"rpo_zero":{"kind":"step","title":"Требуется ли также автоматическое быстрое переключение (низкий RTO)?","detail":"Вам нужна нулевая потеря данных; но как быстро нужно восстановить работу при отказе первичного сервера? Автоматическая отработка отказа за секунды или допустимо короткое окно ручного вмешательства? Это определяет разницу между простой синхронной репликой и полностью автоматизированным кластером.","choices":[{"label":"Требуется автоматическая отработка отказа за секунды","next":"sol_cluster"},{"label":"Достаточно ручного/планового переключения; главное — нулевые потери","next":"sol_sync"}]},"rpo_soft":{"kind":"step","title":"Требуется ли другой регион/география (DR)?","detail":"Если некоторая потеря данных допустима, реальный вопрос — масштаб устойчивости: нужна ли защита от потери целого ЦОД/региона или достаточно защиты от отказа одного узла в том же регионе? Если требуется географическое распределение, на первый план выходит асинхронный standby; если нет, оцениваются варианты с приоритетом стоимости.","choices":[{"label":"Да, отдельный регион для защиты от географических катастроф обязателен","next":"sol_async"},{"label":"Нет, главное — бюджет и простота","next":"budget"}]},"budget":{"kind":"step","title":"Допустимо ли длительное время восстановления (высокий RTO)?","detail":"Вы остаётесь в одном регионе и допускаете некоторую потерю данных. Вы стремитесь к минимальной стоимости или хотите standby-узел, быстро принимающий нагрузку при отказе? Если восстановление в течение часов допустимо, бэкап+PITR сам по себе — самый экономичный путь; иначе нужен тёплый standby.","choices":[{"label":"Да, восстановление в течение часов допустимо (самый дешёвый вариант)","next":"sol_backup"},{"label":"Нет, нужен тёплый standby, принимающий нагрузку за минуты","next":"sol_async"}]},"sol_sync":{"kind":"solution","title":"Рекомендация: синхронная реплика (нулевая потеря данных)","detail":"Первичный сервер не завершает commit, пока хотя бы один standby не подтвердит его; поэтому даже при потере первичного сервера ни одна подтверждённая транзакция не теряется (RPO=0). Идеальное решение в пределах одного региона в сети с низкой задержкой.","steps":["Почему: commit не завершается, пока подтверждённые транзакции не записаны на standby, поэтому потеря данных математически равна нулю.","Когда: финансовые/критичные OLTP-системы, один регион (та же/соседняя зона доступности), сеть с низким RTT.","Пример: PostgreSQL synchronous_commit + synchronous_standby_names, MySQL Group Replication / semisync, Oracle Data Guard SYNC (Maximum Protection/Availability).","Осторожно: синхронное подтверждение добавляет сетевую задержку к каждому commit; если standby замедлится/отключится и синхронная копия только одна, первичный сервер тоже может перестать принимать запись. Проектируйте как минимум с двумя standby или подходящим резервным вариантом.","Осторожно: не реплицируйте синхронно в удалённый регион/с высокой задержкой — производительность записи резко падает. Для географического DR используйте асинхронную репликацию."],"related":["replication-status","promote-failover"]},"sol_async":{"kind":"solution","title":"Рекомендация: асинхронный standby (географический DR)","detail":"Standby применяет изменения потоком, не задерживая commit на первичном сервере. Это стандартный способ держать копию в другом регионе без ущерба для производительности записи; взамен принимается небольшое окно потери данных (равное задержке репликации).","steps":["Почему: первичный сервер не ждёт подтверждения от standby, поэтому репликация в удалённый регион не добавляет задержки записи.","Когда: нужно аварийное восстановление на уровне региона/ЦОД и допустима потеря данных порядка секунд.","Пример: PostgreSQL streaming replication (async), асинхронная репликация MySQL, Oracle Data Guard ASYNC (Maximum Performance).","Осторожно: при отработке отказа можно потерять столько данных, сколько составляет задержка; постоянно отслеживайте задержку репликации и настройте пороговые оповещения.","Осторожно: переключение обычно выполняется вручную или внешней оркестрацией; чёткая процедура отработки отказа и регулярные учения обязательны."],"related":["replication-status","promote-failover"]},"sol_cluster":{"kind":"solution","title":"Рекомендация: кластер с автоматической отработкой отказа","detail":"Кластерное решение, объединяющее мониторинг состояния, выбор лидера и автоматическое переключение; оно нацелено и на низкий RTO (автоматическая отработка отказа за секунды), и на низкий/нулевой RPO с синхронной копией. Операционные затраты выше, но вмешательство человека сводится к минимуму.","steps":["Почему: менеджер кластера автоматически обнаруживает отказ первичного сервера, повышает исправную копию и перенаправляет трафик; никакого ручного вмешательства посреди ночи.","Когда: если обязательны и низкий RTO, и низкий RPO, а операционная команда способна справиться с дополнительной сложностью.","Пример: Patroni/etcd для PostgreSQL, MySQL InnoDB Cluster (Group Replication + MySQL Router), SQL Server Always On Availability Groups, Oracle RAC + Data Guard с Fast-Start Failover (для автоматической отработки отказа нужны FSFO и observer).","Осторожно: риск split-brain реален; не разворачивайте без корректного fencing/кворума (нечётное число узлов, надёжное распределённое хранилище).","Осторожно: сам кластер — новая поверхность отказов; регулярно репетируйте отработку отказа и проверяйте автоматизацию в реальных условиях."],"related":["promote-failover","replication-status"]},"sol_backup":{"kind":"solution","title":"Рекомендация: бэкап + PITR (самый экономичный вариант)","detail":"Вместо работающего standby хранятся только регулярные бэкапы и восстановление на момент времени (PITR) через WAL/архивные журналы. Это самое дешёвое решение; цена — восстановление может занять часы (высокий RTO).","steps":["Почему: нет затрат на постоянно работающий второй узел; хранятся только бэкапы и архивные журналы.","Когда: некритичные системы с высокой терпимостью к простою; среды dev/test; когда главное — бюджет и простота.","Пример: PITR с физическим бэкапом + архивными журналами (PG base backup + WAL, Oracle RMAN + archivelog, MySQL xtrabackup + binlog).","Осторожно: RTO высокий — время восстановления + применения журналов растёт с объёмом данных; измерьте его и убедитесь, что оно укладывается в допустимое время.","Осторожно: непроверенный бэкап — не бэкап; регулярно проводите учения по восстановлению и проверку бэкапов."],"related":["last-backup-status","logical-dump"]}}},"solver/decide-isolation.json":{"id":"decide-isolation","title":"Решение: какой уровень изоляции?","icon":"🧭","category":"Руководство по выбору","intro":"Уровень изоляции транзакций определяет баланс между параллелизмом и согласованностью. Более высокая изоляция означает меньше аномалий, но больше блокировок/конфликтов и повторов. Это дерево помогает выбрать между Read Committed, Repeatable Read/Snapshot и Serializable в зависимости от того, какие аномалии чтения (грязное, неповторяемое, фантомное) вы можете допустить.","start":"root","nodes":{"root":{"kind":"step","title":"Допустимо ли грязное чтение (dirty read)?","detail":"Начните с самой базовой аномалии: должна ли транзакция видеть данные, которые ещё не зафиксированы (и могут быть откачены)? Почти ни одному серьёзному приложению это не нужно; в большинстве современных СУБД уровень по умолчанию уже предотвращает грязное чтение. Его ослабляют только ради приблизительных/аналитических грубых подсчётов и максимальной скорости.","choices":[{"label":"Нет, читать нужно только зафиксированные данные","next":"committed"},{"label":"Да, достаточно грубого/приблизительного чтения (редко)","next":"sol_read_uncommitted"}]},"committed":{"kind":"step","title":"Нужно ли получать тот же результат при повторном чтении в той же транзакции?","detail":"Исключив грязное чтение, следующий вопрос: если вы дважды читаете одну строку в транзакции, проблема ли для вас увидеть другое значение, потому что кто-то зафиксировал изменение между чтениями (неповторяемое чтение), или появление новых строк (фантом)? Логике, требующей согласованности отчётов/расчётов, обычно нужен согласованный снимок.","choices":[{"label":"Нет, допустимо, чтобы каждый оператор видел последние зафиксированные данные","next":"sol_read_committed"},{"label":"Да, нужен согласованный снимок на всю транзакцию","next":"consistent"}]},"consistent":{"kind":"step","title":"Должны ли параллельные транзакции быть логически полностью упорядочены?","detail":"Вам нужно согласованное чтение; этого достаточно, или параллельные транзакции должны давать результат, как если бы выполнялись по одной, последовательно (serializable)? Если вы допускаете тонкие аномалии, такие как write skew (например, две транзакции читают разные строки и пишут так, что делают недействительными друг друга), достаточно Snapshot; иначе требуется полная сериализация.","choices":[{"label":"Нет, согласованного снимка достаточно","next":"sol_repeatable_read"},{"label":"Да, никакие аномалии параллелизма недопустимы","next":"sol_serializable"}]},"sol_read_uncommitted":{"kind":"solution","title":"Рекомендация: Read Uncommitted (только особые случаи)","detail":"Самая низкая изоляция; допускает грязное чтение. Чтение почти не ждёт блокировок, но вы можете увидеть данные, которые будут откачены. Не рекомендуется для приложений общего назначения.","steps":["Почему: читающий не ждёт блокировки пишущего; максимальный параллелизм, но без гарантии согласованности.","Когда: только в таких случаях, как грубые метрики/счётчики прогресса, где точность не важна.","Осторожно: в PostgreSQL этот уровень ведёт себя как Read Committed, а Oracle вообще не предлагает Read Uncommitted; реальное грязное чтение встречается в основном в SQL Server с блокировками (NOLOCK).","Осторожно: «NOLOCK для скорости» не должно становиться постоянной привычкой; есть риск прочитать неверные/дублирующиеся/пропущенные строки."]},"sol_read_committed":{"kind":"solution","title":"Рекомендация: Read Committed (OLTP по умолчанию)","detail":"Каждый оператор видит последние данные, зафиксированные на этот момент; грязное чтение предотвращается. Это уровень по умолчанию в большинстве СУБД, обеспечивающий лучший баланс параллелизма/согласованности для типичных OLTP-нагрузок.","steps":["Почему: предотвращает грязное чтение, не заставляя транзакцию без необходимости долго удерживать блокировки; обеспечивает высокий параллелизм.","Когда: короткие, точечные OLTP-транзакции; правильный уровень по умолчанию для подавляющего большинства веб-приложений.","Пример: Read Committed — уровень по умолчанию в PostgreSQL, SQL Server и Oracle (в Oracle согласованность обеспечивается снимком на уровне оператора).","Осторожно: два чтения в одной транзакции могут вернуть разные результаты (неповторяемое/фантомное чтение); не стройте на нём логику, требующую согласованных отчётов.","Осторожно: в MySQL/InnoDB по умолчанию Repeatable Read; учитывайте разницу между СУБД при миграции."]},"sol_repeatable_read":{"kind":"solution","title":"Рекомендация: Repeatable Read / Snapshot (согласованное чтение)","detail":"Транзакция работает с согласованным снимком, зафиксированным в её начале (или при первом чтении); данные при чтении не меняются в течение транзакции. Благодаря MVCC читающие не блокируют пишущих, а пишущие — читающих.","steps":["Почему: обеспечивает неизменное представление на всю транзакцию; предотвращает неповторяемое чтение (а на практике и фантомы в MVCC-СУБД), делая логику отчётов/расчётов с интенсивным чтением согласованной.","Когда: отчёты, сверки и пакетные расчёты, которые читают одни и те же данные несколько раз в одной транзакции и требуют согласованных результатов.","Пример: MySQL/InnoDB по умолчанию Repeatable Read; SQL Server SNAPSHOT (row versioning); PostgreSQL REPEATABLE READ; Oracle SERIALIZABLE фактически имеет семантику снимка.","Осторожно: не предотвращает аномалии сериализации, такие как write skew; переходите на Serializable, если этого требует бизнес-правило.","Осторожно: Snapshot/row versioning может вызвать рост tempdb или undo/version store; длинные транзакции раздувают старые версии."]},"sol_serializable":{"kind":"solution","title":"Рекомендация: Serializable (полная изоляция)","detail":"Самая высокая изоляция: параллельные транзакции дают результат, как если бы выполнялись последовательно (по одной). Предотвращает все аномалии чтения и write skew; цена — больше конфликтов и необходимость повторов.","steps":["Почему: гарантирует, что результат всегда эквивалентен допустимому последовательному выполнению; устраняет все аномалии параллелизма.","Когда: логика с критичными для корректности инвариантами: проверки баланса/остатков, случаи, когда двойное бронирование недопустимо.","Пример: PostgreSQL SSI (Serializable Snapshot Isolation), SQL Server SERIALIZABLE (range locks); в Oracle истинная сериализация обеспечивается на уровне приложения.","Осторожно: ошибки сериализации (например, 40001 в PG) — ожидаемое поведение; логика повторов, автоматически перезапускающая транзакцию в приложении, ОБЯЗАТЕЛЬНА.","Осторожно: пропускная способность может упасть при нагрузке с большим числом конфликтов; применяйте уровень не везде, а только к транзакциям, где корректность критична."]}}},"solver/decide-sqlnosql.json":{"id":"decide-sqlnosql","title":"Решение: SQL или NoSQL (какое семейство БД)?","icon":"🧭","category":"Руководство по выбору","intro":"Выбор правильного семейства БД определяется формой модели данных, приоритетами согласованности и масштаба, а также сложностью запросов. Обобщения вроде «NoSQL современнее» вводят в заблуждение; каждое семейство рассчитано на определённые паттерны доступа. Исходя из структуры данных, это дерево ведёт к правильному инструменту среди реляционных, документных, key-value и поисковых движков и при необходимости напоминает о полиглотном подходе.","start":"root","nodes":{"root":{"kind":"step","title":"Каков характер данных и преобладающий паттерн доступа?","detail":"Начните с самого определяющего вопроса: данные — это структурированные записи с сильными связями, документы с гибкой/изменчивой схемой, простой поиск по ключу (key-value) или особая задача вроде полнотекстового поиска? Структура данных и способ их запроса во многом определяют семейство СУБД.","choices":[{"label":"Реляционные структурированные записи и сложные запросы","next":"relational_check"},{"label":"Вложенные документы с гибкой/изменчивой схемой","next":"sol_document"},{"label":"Простой key-value, очень низкая задержка/кэширование","next":"sol_keyvalue"},{"label":"Полнотекстовый / фасетный поиск, аналитика журналов","next":"sol_search"}]},"relational_check":{"kind":"step","title":"Приоритет — строгая согласованность (ACID) или горизонтальное масштабирование?","detail":"Данные выглядят реляционными; но каков ваш приоритет? Обязательны ли соединения нескольких таблиц, транзакционная целостность и строгая согласованность (ACID), или нужно горизонтально масштабировать огромный объём, не помещающийся на один узел? Реляционная модель — правильный выбор по умолчанию для большинства нагрузок; её пределы проявляются, только когда масштаб действительно превышает один узел.","choices":[{"label":"Главное — ACID, соединения и целостность сложных запросов","next":"sol_relational"},{"label":"Масштаб превыше всего, слабые связи/соединения","next":"sol_document"}]},"sol_relational":{"kind":"solution","title":"Рекомендация: реляционная база данных (RDBMS)","detail":"Правильный выбор по умолчанию для нагрузок со структурированными, сильно связанными данными, требующих транзакционной целостности. Гарантии ACID, зрелый движок SQL-запросов и соединения позволяют надёжно отвечать на сложные вопросы.","steps":["Почему: обеспечивает ACID-транзакции, целостность внешних ключей и строгую согласованность, а также зрелый планировщик, оптимизирующий сложные запросы с соединением нескольких таблиц.","Когда: OLTP- и отчётные нагрузки, где критичны связи и согласованность: финансы, заказы/склад, ERP/CRM.","Пример: PostgreSQL, MySQL, Oracle, SQL Server; для временных рядов — расширения, такие как PostgreSQL + TimescaleDB.","Осторожно: изменения схемы (миграции) требуют дисциплины; если схема меняется часто и непредсказуемо, рассмотрите документную модель.","Осторожно: при достижении предела одного узла по записи/данным масштабируйтесь с помощью реплик для чтения, секционирования и при необходимости шардинга."],"related":["create-partition"]},"sol_document":{"kind":"solution","title":"Рекомендация: документная база данных","detail":"Хранит гибкие вложенные данные с часто меняющейся схемой в естественном виде (JSON-подобные документы). Подходит для приложений, которые в основном читают/пишут через один документ, почти не нуждаются в соединениях и требуют горизонтального масштабирования.","steps":["Почему: схема гибкая; документы могут добавлять или удалять поля, получать вложенные структуры за одно чтение и хорошо поддаются горизонтальному масштабированию (шардинг).","Когда: документо-ориентированные, изменчивые по структуре, быстро развивающиеся модели данных: каталоги, профили пользователей, контент/CMS, записи событий.","Пример: MongoDB, Couchbase; в реляционном мире JSONB (PostgreSQL) в ограниченной мере даёт схожую гибкость.","Осторожно: сложные соединения нескольких документов и отчётность слабы; если преобладают связи, реляционная модель подходит лучше.","Осторожно: ответственность за согласованность и проверку схемы во многом переходит к приложению; защищайте качество данных в коде."]},"sol_keyvalue":{"kind":"solution","title":"Рекомендация: хранилище key-value / кэш","detail":"Рассчитано на нагрузки, которым нужны очень быстрые чтение/запись по ключу и не нужны сложные запросы. Обычно работает в памяти и обеспечивает задержку в микро-/миллисекунды; идеально для таких задач, как кэширование, сессии и счётчики.","steps":["Почему: крайне низкая задержка и высокая пропускная способность при простом доступе по ключу; структуры в памяти снимают нагрузку с реляционной базы данных.","Когда: уровень кэша, хранилище сессий, счётчики rate-limit, таблицы лидеров, временные очереди/pub-sub.","Пример: Redis, Memcached; Redis также предлагает богатые структуры, такие как lists/sets/sorted sets, и варианты персистентности.","Осторожно: обычно это не единственный источник истины; храните исходные данные в надёжной базе данных, а здесь — производные/кэшированные данные.","Осторожно: память ограничена и дорога; планируйте политику вытеснения, TTL и настройки персистентности/надёжности в соответствии с потребностями бизнеса."]},"sol_search":{"kind":"solution","title":"Рекомендация: поисковый / аналитический движок","detail":"Движок на основе инвертированного индекса, специализированный на полнотекстовом поиске, ранжировании по релевантности, фасетной фильтрации и аналитике больших объёмов журналов/текста. Он даёт возможности поиска, недоступные реляционным запросам с LIKE.","steps":["Почему: благодаря инвертированному индексу и оценке релевантности выполняет полнотекстовый поиск, автодополнение, фасеты и агрегации с высокой производительностью.","Когда: поиск по товарам/сайту, аналитика журналов и наблюдаемости, приложения, которым нужен поиск с ранжированием по релевантности.","Пример: Elasticsearch/OpenSearch, Apache Solr; для полнотекстового поиска среднего масштаба может хватить PostgreSQL tsvector.","Осторожно: обычно это не основная учётная система; храните основные данные в реляционной/документной БД и наполняйте поисковый индекс оттуда (CDC/синхронизация).","Осторожно: гарантии строгой согласованности в реальном времени слабы (индексация почти в реальном времени); учитывайте задержку индексации при проектировании."],"related":["setup-cdc-replication"]}}},"solver/decide-shard-replica.json":{"id":"decide-shard-replica","title":"Решение: шардинг или репликация (масштабирование)?","icon":"🧭","category":"Руководство по выбору","intro":"Решение о масштабировании БД зависит от того, где узкое место — в чтении или записи, и помещаются ли данные на один узел. Неверная стратегия означает лишнюю сложность или нерешённое узкое место. Это дерево сначала напоминает о самых дешёвых и простых вариантах (вертикальное масштабирование, кэширование), а затем помогает правильно выбрать между репликами для чтения и шардингом.","start":"root","nodes":{"root":{"kind":"step","title":"Вы сначала попробовали самый простой путь: вертикальное масштабирование и оптимизацию запросов?","detail":"Прежде чем переходить к распределённой архитектуре, возьмите самый дешёвый выигрыш: наращивание оборудования (CPU/RAM/IOPS), добавление недостающих индексов и исправление дорогих запросов решают большинство узких мест. Добавляйте сложность, только когда эти возможности исчерпаны.","choices":[{"label":"Нет / я ещё не упёрся в предел","next":"sol_vertical"},{"label":"Да, вертикального масштабирования и оптимизации недостаточно","next":"bottleneck"}]},"bottleneck":{"kind":"step","title":"Узкое место — чтение или запись/объём данных?","detail":"Определите источник проблемы масштабирования: множество запросов на чтение перегружает один узел (нагрузка с преобладанием чтения), или скорость записи либо общий объём данных превышают возможности одного узла (преобладание записи/объёма)? Это различие — фундаментальная разница между репликами и шардингом.","choices":[{"label":"Преобладает трафик чтения, запись помещается на один узел","next":"read_path"},{"label":"Скорость записи или объём данных превышают один узел","next":"sol_sharding"}]},"read_path":{"kind":"step","title":"Чтения повторяющиеся/кэшируемые или свежие и разнообразные?","detail":"Разберите узкое место чтения ещё на шаг: одни и те же результаты читаются снова и снова (кэш очень эффективен) или чтения запрашивают разнообразные, широкие и актуальные данные (нужна дополнительная мощность чтения)? Это определяет выбор между уровнем кэша и репликами для чтения.","choices":[{"label":"Одни и те же данные читаются многократно, небольшое устаревание допустимо","next":"sol_cache"},{"label":"Разнообразные/свежие чтения, нужна чистая мощность чтения","next":"sol_read_replica"}]},"sol_vertical":{"kind":"solution","title":"Рекомендация: сначала вертикальное масштабирование + оптимизация запросов","detail":"Первый и самый дешёвый путь перед переходом к распределённой архитектуре. Наращивание оборудования и исправление запросов/индексов решают большинство проблем масштабирования без добавления операционной сложности.","steps":["Почему: минимальные сложность и риск; оставаясь на одном узле, вы не меняете модель транзакций/согласованности и не вынуждены разделять код приложения.","Когда: если вы ещё не достигли аппаратного потолка одного узла или узкое место на самом деле вызвано плохими запросами/недостающими индексами.","Шаг: профилируйте медленные запросы, добавьте недостающие индексы, исправьте планы/статистику; затем увеличьте CPU/RAM/IOPS.","Осторожно: вертикальное масштабирование конечно, а его стоимость растёт экспоненциально; один узел по-прежнему единая точка отказа (для HA отдельно настройте реплику).","Осторожно: заранее планируйте следующий шаг (реплики/шардинг) по мере приближения к потолку; поспешные миграции рискованны."]},"sol_read_replica":{"kind":"solution","title":"Рекомендация: реплики для чтения (масштабирование чтения)","detail":"Запись остаётся на одном узле (первичном), а трафик чтения распределяется по одной или нескольким репликам. Это стандартный способ увеличить мощность чтения и устойчивость для нагрузок с преобладанием чтения.","steps":["Почему: запросы на чтение распределяются по нескольким копиям; первичный сервер обрабатывает только запись, а пропускная способность чтения растёт почти линейно.","Когда: нагрузки с высоким соотношением чтения/записи; вынос отчётных и аналитических запросов с первичного сервера; одновременное получение HA-standby.","Пример: потоковые реплики PostgreSQL, реплики MySQL + маршрутизация через прокси, разделение чтения/записи в приложении (read-write split).","Осторожно: при асинхронных репликах задержка репликации создаёт проблему read-your-writes (только что записанное нельзя сразу прочитать); направляйте критичные чтения на первичный сервер.","Осторожно: реплики не решают проблему узкого места записи; единственный пишущий узел по-прежнему остаётся пределом — если запись тоже узкое место, требуется шардинг."],"related":["replication-status"]},"sol_cache":{"kind":"solution","title":"Рекомендация: уровень кэша","detail":"Хранение часто перечитываемых результатов в быстром слое в памяти резко снижает нагрузку на базу данных. При правильном паттерне он отвечает, не обращаясь к базе данных, снижая и задержку, и нагрузку.","steps":["Почему: повторные чтения обслуживаются со скоростью памяти, вообще не доходя до базы данных; это метод масштабирования чтения с наилучшим соотношением пользы и затрат.","Когда: одни и те же данные читаются много раз и небольшое устаревание допустимо; например, сведения о товарах, настройки, справочные данные.","Пример: паттерн cache-aside с Redis/Memcached; приложение сначала обращается к кэшу, а при промахе — к базе данных.","Осторожно: инвалидация кэша сложна; явно спроектируйте TTL и стратегию обновления/очистки при записи.","Осторожно: не полагайтесь на устаревший кэш для чтений, требующих строгой согласованности (деньги/остатки); обслуживайте такие чтения из источника."]},"sol_sharding":{"kind":"solution","title":"Рекомендация: шардинг (масштабирование записи/данных)","detail":"Данные делятся по ключу шардинга и распределяются по нескольким независимым узлам; каждый узел владеет частью данных и их записью. Это способ масштабировать скорость записи и общий объём данных, но он существенно добавляет сложности.","steps":["Почему: запись и хранение распределяются по нескольким узлам; ограничения одного пишущего узла и данных одного узла исчезают, открывается горизонтальное масштабирование.","Когда: пропускная способность записи или объём данных больше не помещаются на один узел (и реплики для чтения); когда других вариантов не осталось.","Пример: маршрутизация по шардам на уровне приложения, Vitess (MySQL), Citus (PostgreSQL), встроенный шардинг в документных БД.","Осторожно: выбор ключа шардинга критичен и трудно обратим; плохой ключ создаёт горячие точки и неравномерное распределение. Выберите ключ с высокой кардинальностью, соответствующий паттерну доступа.","Осторожно: соединения, транзакции и уникальность между шардами усложняются; сложность приложения, перебалансировка (решардинг) и операционная нагрузка существенно возрастают."],"related":["create-partition"]}}},"solver/decide-backup.json":{"id":"decide-backup","title":"Решение: какая стратегия бэкапа?","icon":"🧭","category":"Руководство по выбору","intro":"Стратегия бэкапа выбирается балансом трёх факторов: какую потерю данных вы можете допустить (RPO), объёма данных и того, как быстро нужно восстановиться (RTO). Ни один метод не подходит для всех сценариев. Исходя из цели RPO, это дерево ведёт к выбору между логическими дампами, физическими бэкапами + PITR и снимками и напоминает, что 3-2-1 и проверка не обсуждаются.","start":"root","nodes":{"root":{"kind":"step","title":"Какова цель по потере данных (RPO)?","detail":"Начните с самого определяющего ограничения: данные за сколько минут/часов вы можете позволить себе потерять при аварии? Если требуется потеря, близкая к нулю, непрерывное архивирование журналов (PITR) обязательно; если допустимы ежедневные/еженедельные бэкапы, достаточно более простых методов. RPO задаёт рамки стратегии.","choices":[{"label":"Почти ноль — нужно иметь возможность вернуться к любому моменту времени","next":"sol_physical_pitr"},{"label":"Ежедневные/периодические бэкапы (потеря нескольких часов) допустимы","next":"size"},{"label":"Сначала напомните общие лучшие практики (3-2-1 / проверка)","next":"sol_321"}]},"size":{"kind":"step","title":"Каковы объём данных и требования к переносимости?","detail":"Если периодических бэкапов достаточно, следующий вопрос — масштаб и назначение: база данных небольшая/средняя и вы хотите, чтобы бэкап переносился в другую среду/версию, или данные большие и главное — вернуться в строй за минуты? Это определяет выбор между логическим дампом и снимком.","choices":[{"label":"Небольшой/средний объём, переносимость и совместимость между версиями","next":"sol_logical"},{"label":"Большие данные, восстановление должно быть очень быстрым (низкий RTO)","next":"sol_snapshot"}]},"sol_logical":{"kind":"solution","title":"Рекомендация: логический дамп","detail":"Выгружает данные на логическом уровне (определения схемы + строки); результат — SQL/текст (pg_dump plain format, mysqldump) или специфичный для инструмента двоичный дамп (pg_dump -Fc, Oracle Data Pump). Гибкий и переносимый метод для небольших и средних БД, миграций, обновлений версий и восстановления выбранных таблиц/объектов.","steps":["Почему: результат переносим (и читаем в текстовом виде); подходит для переноса между версиями/СУБД и восстановления отдельной таблицы/объекта.","Когда: небольшой/средний объём данных, обновления версий/миграции, копии для среды разработки, потребность в логическом (построчном) восстановлении.","Пример: PostgreSQL pg_dump/pg_dumpall, MySQL mysqldump, Oracle Data Pump (expdp/impdp; создаёт двоичный .dmp, используйте impdp SQLFILE=, чтобы увидеть DDL в текстовом виде).","Осторожно: поскольку восстановление заново обрабатывает данные, и создание бэкапа, и восстановление для больших БД очень медленные (высокий RTO).","Осторожно: сам по себе не обеспечивает низкий RPO; восстановление на момент времени требует физического бэкапа + архива журналов. Для согласованного снимка используйте соответствующие параметры согласованности."],"related":["logical-dump","last-backup-status"]},"sol_physical_pitr":{"kind":"solution","title":"Рекомендация: физический бэкап + WAL/архивные журналы (PITR)","detail":"Вы регулярно делаете физические базовые бэкапы и непрерывно архивируете журналы транзакций (WAL/redo/binlog). Это позволяет выполнить восстановление на момент времени (Point-in-Time Recovery) к любой точке и свести потерю данных почти к нулю.","steps":["Почему: благодаря непрерывному архивированию журналов можно докатиться до любой транзакции после последнего базового бэкапа и вернуться к нужному моменту (например, непосредственно перед ошибочной командой) — минимальный RPO.","Когда: большие и критичные продуктивные БД; системы, которым нужны низкий RPO и разумный RTO.","Пример: PostgreSQL base backup + архив WAL (pgBackRest/barman), Oracle RMAN + archivelog, физический бэкап MySQL + binlog (xtrabackup).","Осторожно: настройка и эксплуатация сложнее; постоянно следите, чтобы архивные журналы поступали без перерывов и пропусков — один пропуск разрывает цепочку PITR.","Осторожно: хранилище архива журналов постоянно растёт; без политики хранения и плана хранилища диск заполнится."],"related":["last-backup-status","logical-dump"]},"sol_snapshot":{"kind":"solution","title":"Рекомендация: снимок (snapshot)","detail":"Создаёт снимок данных на уровне блоков на уровне хранения или виртуализации. Бэкап и восстановление очень быстры даже для очень больших БД; однако метод зависит от инфраструктуры и требует аккуратного использования.","steps":["Почему: копия на уровне блоков создаётся почти мгновенно, а восстановление очень быстрое; даёт минимальный RTO для больших данных.","Когда: терабайты данных, приоритет — быстрое восстановление; если уровень хранения/облака поддерживает снимки.","Пример: снимок SAN/LVM, снимок облачного диска (EBS и т. п.), снимок ZFS/файловой системы; запускается согласованно с СУБД.","Осторожно: снимок зависит от системы хранения и обычно находится в той же инфраструктуре; сам по себе он не удовлетворяет правилу 3-2-1 — копируйте его за пределы площадки.","Осторожно: чтобы снимок был согласован на уровне приложения, в момент его создания база данных должна быть в соответствующем режиме (fsfreeze/backup mode/hot backup); иначе получится бэкап, согласованный лишь как после сбоя (crash-consistent)."],"related":["last-backup-status"]},"sol_321":{"kind":"solution","title":"Рекомендация: правило 3-2-1 + внешняя площадка + обязательная проверка","detail":"Какой бы метод вы ни выбрали, эти принципы должны стоять поверх стратегии. Самого наличия бэкапа недостаточно: он должен быть на разных носителях, в другом месте и регулярно проверяться. Это рамка, дополняющая все варианты выше.","steps":["3-2-1: храните не менее 3 копий на 2 разных типах носителей, 1 копию — за пределами площадки.","Почему за пределами площадки: пожар, наводнение, программа-вымогатель или потеря региона могут разом уничтожить все копии в одном месте; хотя бы одна копия должна быть изолированной/удалённой.","Проверка обязательна: непроверенный бэкап — не бэкап; регулярно проводите реальные учения по восстановлению и измеряйте время восстановления (RTO).","Неизменяемость: для защиты от программ-вымогателей храните неудаляемую/неизменяемую (immutable/WORM) или изолированную (air-gapped) копию.","Осторожно: автоматически отслеживайте целостность (checksum) и восстанавливаемость бэкапов; настройте оповещения о неудачных/устаревших бэкапах. Шифруйте и сами бэкапы."],"related":["last-backup-status"]}}},"solver/decide-index.json":{"id":"decide-index","title":"Решение: какой тип индекса?","icon":"🧭","category":"Руководство по выбору","intro":"Правильный тип индекса зависит от паттерна доступа запроса (равенство, диапазон или текстовый/JSON-поиск), кардинальности столбца и баланса чтения/записи. Неверный индекс и тратит место, и замедляет запись. Это дерево исходит из формы запроса, ведёт к выбору между B-tree, Hash, Bitmap, GIN/GiST, частичным и покрывающим индексами и напоминает, что порядок столбцов в составном индексе критичен.","start":"root","nodes":{"root":{"kind":"step","title":"Как ваш запрос использует этот столбец?","detail":"Паттерн доступа — самый определяющий фактор при выборе индекса. Вы только сравниваете столбец через '=', используете диапазон/сортировку (>, <, BETWEEN, ORDER BY) или ищете по полному тексту, полю JSON либо геометрическим данным/массивам? Начните отсюда; не выбирайте тип индекса, пока потребность не ясна.","choices":[{"label":"Смешанные равенство и диапазон/сортировка (=, >, <, BETWEEN, ORDER BY)","next":"cardinality"},{"label":"Только равенство (=), без диапазона и сортировки","next":"sol_hash"},{"label":"Полнотекстовый поиск, поле JSON/JSONB, массив или геометрический поиск","next":"sol_gin_gist"},{"label":"Запрос всегда нацелен на одно и то же небольшое подмножество строк (например, WHERE status='active')","next":"sol_partial"}]},"cardinality":{"kind":"step","title":"Какова кардинальность столбца (число различных значений)?","detail":"Если нужны диапазон/сортировка, по умолчанию используется B-tree; однако для столбцов с очень малым числом различных значений (низкая кардинальность: пол, статус, да/нет) в хранилище данных с преобладанием чтения лучше может подойти индекс Bitmap. У столбца много уникальных значений или несколько повторяющихся?","choices":[{"label":"Высокая кардинальность (много уникальных значений) или OLTP-нагрузка","next":"coverage"},{"label":"Низкая кардинальность и преобладание чтения / хранилище данных (DWH)","next":"sol_bitmap"}]},"coverage":{"kind":"step","title":"Читает ли запрос только столбцы, входящие в индекс?","detail":"Паттерн доступа подходит для B-tree. Последняя оптимизация: если все столбцы, которые запрос возвращает и по которым фильтрует, есть в индексе, СУБД может ответить только по индексу, не обращаясь к таблице (index-only / покрывающее сканирование). Ваш запрос читает несколько столбцов или ему нужна большая часть строки?","choices":[{"label":"Да, несколько столбцов; можно ответить без обращения к таблице","next":"sol_covering"},{"label":"Нет, достаточно стандартного доступа","next":"sol_btree"}]},"sol_btree":{"kind":"solution","title":"Рекомендация: B-tree (по умолчанию)","detail":"B-tree — сбалансированная древовидная структура, поддерживающая широкий спектр паттернов доступа: равенство, диапазон, сортировку (ORDER BY), сопоставление по префиксу с LIKE 'abc%' и MIN/MAX. Это тип индекса по умолчанию в большинстве реляционных СУБД и правильная отправная точка при сомнениях.","steps":["Почему: одна структура эффективно обслуживает и равенство, и диапазон/сортировку; это самый универсальный индекс.","Когда: OLTP-запросы, WHERE + диапазон + ORDER BY вместе, первичные/уникальные ключи.","ПОРЯДОК столбцов в составном индексе критичен: первым ставьте самый селективный столбец, используемый в условии равенства, а столбец диапазона — последним. Индекс (a,b) помогает фильтру по 'a' или 'a,b'; фильтру только по 'b' он, как правило, не помогает (правило левого префикса).","Пример: стандартный индекс B-tree/B+tree одинаково в PostgreSQL/Oracle/MySQL(InnoDB)/MSSQL.","Осторожно: каждый индекс увеличивает стоимость записи (INSERT/UPDATE/DELETE) и занимаемое место на диске; не держите ненужные или дублирующие индексы."],"related":["slow-query"]},"sol_hash":{"kind":"solution","title":"Рекомендация: хеш-индекс (только равенство)","detail":"Хеш-индекс хранит хеш значения столбца и очень быстр только для поиска по точному равенству ('='). Он не поддерживает поиск по диапазону, сортировку и поиск по префиксу, поэтому область его применения узка.","steps":["Почему: обеспечивает в среднем доступ за постоянное время (O(1)) при поиске по равенству; может давать преимущество для очень больших нагрузок с поиском по равенству, помещающихся в память.","Когда: столбец используется только с '=' и диапазон/сортировка никогда не нужны.","Различия СУБД: в PostgreSQL есть хеш-индексы, но на практике в большинстве случаев B-tree достаточен и гибче; MySQL(InnoDB) управляет хеш-индексацией только внутренне как adaptive hash index, а движок MEMORY использует hash явно; в Oracle хеш встречается в контексте кластеров/hash clusters.","Осторожно: диапазон, BETWEEN, ORDER BY и LIKE 'abc%' не поддерживаются; как только такие потребности появятся, выбирайте B-tree.","Осторожно: поскольку стандартного B-tree обычно достаточно, не переходите на hash без измерений."],"related":["slow-query"]},"sol_bitmap":{"kind":"solution","title":"Рекомендация: индекс Bitmap (низкая кардинальность / DWH)","detail":"Индекс Bitmap хранит битовый массив для каждого различного значения и очень быстро объединяет несколько условий с низкой кардинальностью побитовыми AND/OR. Он силён в нагрузках с преобладанием чтения, таких как хранилища данных и отчётность.","steps":["Почему: занимает очень мало места на столбцах с небольшим числом различных значений и эффективно объединяет несколько условий (WHERE gender=.. AND region=..) битовыми операциями.","Когда: столбцы с низкой кардинальностью, запросы по схеме «звезда», хранилище данных/отчётность, нагрузки с преобладанием чтения.","Различия СУБД: классическая сильная сторона Oracle; в PostgreSQL нет постоянного индекса Bitmap, но планировщик может строить 'bitmap heap scan' во время выполнения. В MySQL/MSSQL постоянного индекса Bitmap как такового нет.","Осторожно: не используйте на часто обновляемых (OLTP) таблицах; блокировки bitmap сильно снижают параллелизм на уровне строк.","Осторожно: на столбцах с высокой кардинальностью преимущество исчезает; там предпочтителен B-tree."],"related":["slow-query"]},"sol_gin_gist":{"kind":"solution","title":"Рекомендация: GIN / GiST (текст, JSON, массивы, геометрия)","detail":"В PostgreSQL есть специализированные семейства индексов для типов доступа, с которыми стандартный B-tree не справляется. GIN указывает на множество компонентов внутри значения (слова, ключи JSONB, элементы массива); GiST подходит для геометрических/диапазонных запросов и поиска ближайших соседей.","steps":["Почему: для этих типов данных запросы вида «содержит/пересекается/рядом» B-tree не может обслужить эффективно; GIN/GiST их ускоряют.","Когда GIN: полнотекстовый поиск (tsvector), поиск по полям/ключам JSONB (@>, ?), запросы на вхождение в массив; много чтения, мало записи.","Когда GiST: геометрия/геопространственные данные (PostGIS), диапазонные типы, запросы ближайших соседей (KNN).","Различия СУБД: GIN/GiST специфичны для PostgreSQL. Аналогичную задачу решают индексы FULLTEXT и SPATIAL (R-tree) в MySQL, Oracle Text и Spatial в Oracle и Full-Text Index в MSSQL.","Осторожно: GIN дорог при записи и медленно строится (можно смягчить с помощью fastupdate); измерьте затраты на таблицах с интенсивной записью."],"related":["slow-query"]},"sol_partial":{"kind":"solution","title":"Рекомендация: частичный / фильтрованный индекс","detail":"Частичный индекс индексирует только строки, удовлетворяющие условию (WHERE ...), а не всю таблицу. Если запросы всегда нацелены на одно и то же небольшое подмножество, индекс становится намного меньше и быстрее, а стоимость записи снижается.","steps":["Почему: поскольку индексируются только нужные строки, снижаются и занимаемое место, и затраты на обслуживание; часто запрашиваемое «горячее» подмножество становится намного быстрее.","Когда: стабильные узкие условия, такие как WHERE status='active', deleted_at IS NULL, priority='high'.","Различия СУБД: PostgreSQL поддерживает это напрямую через 'partial index' (CREATE INDEX ... WHERE ...), MSSQL — через 'filtered index'. В Oracle для аналога используют функциональные/условные индексы; MySQL(InnoDB) напрямую это не поддерживает.","Осторожно: условие WHERE индекса должно совпадать с условием запроса; иначе планировщик не сможет использовать индекс.","Осторожно: если подмножество растёт, преимущество уменьшается; убедитесь, что условие стабильно."],"related":["slow-query"]},"sol_covering":{"kind":"solution","title":"Рекомендация: покрывающий (INCLUDE) индекс","detail":"Покрывающий индекс содержит все столбцы, нужные запросу, что позволяет СУБД ответить только по индексу, вообще не обращаясь к таблице (heap) (index-only scan). Даёт значительное ускорение для часто выполняемых запросов, возвращающих мало столбцов.","steps":["Почему: обращение к таблице (heap fetch) исключается; дисковый ввод-вывод и задержка заметно снижаются.","Когда: часто выполняемые запросы с чётко определёнными столбцами WHERE + SELECT и узким результирующим набором.","Различия СУБД: MSSQL и PostgreSQL добавляют неключевые столбцы в листовой уровень индекса с помощью 'INCLUDE'; в MySQL(InnoDB) вторичные индексы уже содержат первичный ключ, поэтому покрытие достигается составным индексом; в Oracle доступ только по индексу происходит, когда все нужные столбцы есть в индексе.","Осторожно: лишние/широкие столбцы INCLUDE раздувают индекс и замедляют запись; добавляйте только действительно нужные столбцы.","Осторожно: в PostgreSQL index-only scan требует актуальной карты видимости (visibility map); регулярный VACUUM обязателен."],"related":["slow-query"]}}},"solver/decide-partition.json":{"id":"decide-partition","title":"Решение: какая стратегия секционирования?","icon":"🧭","category":"Руководство по выбору","intro":"Секционирование разбивает большую таблицу на несколько физических частей под одним логическим именем, упрощая удаление старых данных и позволяя запросам обращаться только к нужным частям (partition pruning). Однако на небольших таблицах оно лишь добавляет сложности. Это дерево сначала проверяет, действительно ли нужно секционирование, а затем ведёт к выбору между Range, List, Hash и Composite в зависимости от паттернов доступа и распределения.","start":"root","nodes":{"root":{"kind":"step","title":"Действительно ли таблица настолько велика, что нужно секционирование?","detail":"Секционирование — не волшебство для производительности, а инструмент масштаба и обслуживания (жизненного цикла). Если таблица недостаточно велика или запросы уже хорошо работают с индексами, секционирование приносит лишь сложность планов и накладные расходы на управление. Ваша таблица достигает десятков миллионов+ строк/сотен ГБ или пока небольшая/средняя?","choices":[{"label":"Да, она очень большая и/или старые данные нужно массово удалять/архивировать","next":"access"},{"label":"Нет, таблица небольшая/средняя, или я не уверен","next":"sol_no_partition"}]},"access":{"kind":"step","title":"Как вы обращаетесь к данным и как их следует разделить?","detail":"Ключом секционирования должен быть столбец, по которому фильтрует большинство запросов; только тогда срабатывает отсечение секций. Каков преобладающий паттерн доступа? По датам/последовательным диапазонам (последние 30 дней), по определённым значениям категории/региона, или явного фильтра нет и нужно равномерно распределить нагрузку?","choices":[{"label":"Последовательные диапазоны, такие как дата/порядковый номер (последние X дней, по месяцам)","next":"sol_range"},{"label":"Дискретные наборы значений, такие как категория/регион/арендатор","next":"sol_list"},{"label":"Нет явного диапазона/категории; цель — равномерно распределить нагрузку","next":"sol_hash"},{"label":"И дата, и большой объём; нужно делить по двум измерениям","next":"sol_composite"}]},"sol_no_partition":{"kind":"solution","title":"Рекомендация: не секционировать (пока)","detail":"На небольших/средних таблицах секционирование не даёт выгоды; достаточно правильно подобранных индексов и обычного обслуживания. Преждевременное секционирование усложняет планы запросов, теряет отсечение секций при неверном ключе и добавляет операционные накладные расходы.","steps":["Почему: на небольшой таблице полное сканирование или доступ по индексу и так быстры; секционирование добавляет лишь дополнительное управление и сложность планов.","Когда оставаться так: меньше нескольких миллионов строк, нет необходимости массово удалять старые данные, запросы хорошо работают с индексами.","Вместо этого: правильные индексы, при необходимости архивная таблица, регулярные статистика/VACUUM/ANALYZE.","Осторожно: не настраивайте секционирование заранее в расчёте на то, что «она вырастет»; переходите, когда рост доказан и правильный ключ ясен.","Различия СУБД: общий принцип для всех СУБД; декларативное секционирование PostgreSQL, MySQL PARTITION BY, а также секционирование Oracle и MSSQL легко добавить позже."],"related":[]},"sol_range":{"kind":"solution","title":"Рекомендация: секционирование по диапазонам (Range)","detail":"Секционирование по диапазонам делит строки по последовательным диапазонам столбца (например, диапазоны дат: одна секция на месяц/квартал). Это самая распространённая и мощная стратегия для временных рядов и постоянно растущих таблиц.","steps":["Почему: когда запросы фильтруют по дате/диапазону, СУБД читает только нужные секции (partition pruning); старые данные удаляются мгновенно одной командой (DROP секции).","Когда: временные ряды, журналы, история заказов/транзакций; запросы «последние N дней» и архивирование по времени.","Ключ секционирования: выберите столбец даты/последовательности, присутствующий в WHERE подавляющего большинства запросов; иначе отсечение секций не работает.","Пример: секционирование RANGE в PostgreSQL, range/interval partitioning в Oracle (автоматические новые секции), MySQL PARTITION BY RANGE (для дат обычно RANGE COLUMNS), partition function/scheme в MSSQL.","Осторожно: автоматизируйте создание новых секций для будущих диапазонов (Oracle interval или задание по расписанию); заполненная секция 'default/maxvalue' становится горячей точкой."],"related":[]},"sol_list":{"kind":"solution","title":"Рекомендация: секционирование по списку (List)","detail":"Секционирование по списку группирует строки по дискретным известным значениям столбца (например, region = 'TR','EU','US' или ID арендатора). Когда есть естественные конечные категории, оно делает доступ и управление понятными.","steps":["Почему: когда запросы фильтруют по определённым значениям категории, читается только эта секция; обслуживание/удаление по категориям упрощается.","Когда: дискретные и относительно стабильные наборы значений, такие как регион/страна, тип продукта, статус, разделение арендаторов (multi-tenant).","Ключ секционирования: набор значений должен быть конечным и предсказуемым; если новые значения добавляются часто, рассмотрите Hash вместо List.","Пример: секционирование LIST в PostgreSQL, list partitioning в Oracle, MySQL PARTITION BY LIST, partition scheme с подходящими границами в MSSQL.","Осторожно: держите секцию 'default' для неопределённых/новых значений; иначе строку невозможно вставить и вы получите ошибку."],"related":[]},"sol_hash":{"kind":"solution","title":"Рекомендация: хеш-секционирование (равномерное распределение)","detail":"Хеш-секционирование распределяет строки относительно равномерно по заранее заданному числу секций на основе хеша ключевого столбца. Используется, чтобы избежать горячих точек и сбалансировать нагрузку, когда нет естественного диапазона/категории.","steps":["Почему: строки распределяются равномерно; горячая точка записи, скапливающаяся в одной секции или в конце первичного ключа, уменьшается.","Когда: высокий объём записи, нет естественного диапазона/категории, цель — выровнять ввод-вывод и размер (например, id с высокой кардинальностью).","Ключ секционирования: выберите столбец с высокой кардинальностью и равномерным распределением (обычно первичный/уникальный ключ).","Пример: секционирование HASH в PostgreSQL, hash partitioning в Oracle, MySQL PARTITION BY HASH/KEY.","Осторожно: для диапазонных запросов отсечения секций нет (хеширование разрушает порядок); запросы вида «последние N дней» сканируют все секции. Изменение числа секций позже требует перераспределения."],"related":[]},"sol_composite":{"kind":"solution","title":"Рекомендация: составное (многоуровневое) секционирование","detail":"Составное секционирование сочетает две стратегии: сначала делит на верхнем уровне, затем разбивает каждую секцию на подсекции (например, Range-by-Hash или Range-by-List). Используется, когда одновременно нужны жизненный цикл по времени и равномерное распределение/разделение по категориям.","steps":["Почему: когда одной стратегии недостаточно, удовлетворяет обе потребности сразу; например, деление по месяцам (архивирование) и распределение по хешу внутри месяца (предотвращение горячих точек).","Когда: очень большие временные ряды + высокая параллельная запись или двойной паттерн доступа «дата + регион/арендатор».","Пример: составное секционирование Oracle (range-hash, range-list, list-hash и т. д.); в PostgreSQL строится вручную повторным секционированием секций (partition of partition); подсекции MySQL (RANGE/LIST + HASH/KEY).","Осторожно: это вариант с наибольшей сложностью управления и схемы; выбирайте его, только когда оба измерения — доказанная потребность.","Осторожно: неверный выбор верхнего/нижнего ключа лишает отсечения секций; убедитесь, что оба уровня соответствуют паттерну запросов."],"related":[]}}},"solver/decide-pooling.json":{"id":"decide-pooling","title":"Решение: как настроить пул соединений?","icon":"🧭","category":"Руководство по выбору","intro":"Каждое соединение с БД несёт затраты памяти и планирования; неконтролируемый рост числа соединений замедляет сервер или даже выводит его из строя. Пул соединений решает это, многократно используя небольшое число постоянных соединений. Это дерево помогает решить, где размещается пул, какой режим использовать (session/transaction/statement) и каков правильный размер, а также подчёркивает, почему внешний пул обязателен в serverless-средах.","start":"root","nodes":{"root":{"kind":"step","title":"Как подключается архитектура вашего приложения?","detail":"Решение о пуле начинается с архитектуры. Это один долгоживущий процесс приложения (классический сервер), множество независимых процессов/экземпляров (микросервисы, много подов) или serverless/Lambda-функции, которые создаются и завершаются с каждым запросом? Каждый вариант требует своего размещения пула.","choices":[{"label":"Один/несколько долгоживущих процессов приложения","next":"sol_app_pool"},{"label":"Много процессов/экземпляров/подов подключаются к одной БД","next":"middleware"},{"label":"Serverless / Lambda / короткоживущие функции","next":"sol_serverless"},{"label":"Сначала покажите, как выбрать правильный размер пула","next":"sol_size"}]},"middleware":{"kind":"step","title":"Как выбрать режим промежуточного пула?","detail":"При большом числе клиентов промежуточный пул (PgBouncer, ProxySQL) удерживает число соединений с БД на низком уровне. Но режим пула зависит от функций, используемых приложением: полагаетесь ли вы на состояние, привязанное к сессии (подготовленные операторы, временные таблицы, SET, advisory locks, функции уровня сессии), или всё завершается в пределах одной транзакции?","choices":[{"label":"Я завишу от состояния сессии (SET, временные таблицы, подготовленные операторы сессии)","next":"sol_session_mode"},{"label":"Транзакции короткие и независимые; состояние сессии не храню","next":"sol_txn_mode"}]},"sol_app_pool":{"kind":"solution","title":"Рекомендация: пул соединений внутри приложения","detail":"Для одного/нескольких долгоживущих процессов большинство потребностей покрывает библиотека пула внутри приложения (HikariCP, встроенный пул database/sql в Go, connection pooling в .NET). Соединения открываются один раз и используются многократно.","steps":["Почему: затраты на открытие/закрытие соединений исчезают; ограниченное и предсказуемое число соединений защищает БД.","Когда: классический монолит или несколько экземпляров приложения; общая потребность в соединениях ниже ёмкости БД.","Пример: Java HikariCP, пулы .NET SqlConnection/Npgsql, Node pg-pool, пул Python SQLAlchemy.","Определение размера: задайте размер пула исходя из числа ядер/дисков (следующее решение); «чем больше, тем лучше» здесь не работает.","Осторожно: каждый экземпляр приложения открывает собственный пул; число экземпляров × размер пула не должно превышать max_connections БД. Если превышает, переходите на промежуточный пул."],"related":["connection-storm"]},"sol_size":{"kind":"solution","title":"Рекомендация: правильно задайте размер пула","detail":"Пул не должен быть ни слишком маленьким (запросы ждут), ни слишком большим. Слишком много соединений порождают переключения контекста CPU и конкуренцию за блокировки и снижают пропускную способность. Размер — не число, которое нужно наращивать бесконечно, а небольшое значение, зависящее от ядер и ввода-вывода.","steps":["Начальная формула: распространённая отправная точка — размер пула ≈ (число ядер × 2) + эффективное число шпинделей; на SSD/многоядерных системах хорошее начало — около «ядра × 2-4».","Почему держать его небольшим: реальный параллелизм БД ограничен числом ядер; лишние соединения просто стоят в очереди и замедляют всё из-за переключений контекста/конкуренции за блокировки.","Правило: при сотнях параллельных клиентов решение — не увеличивать пул, а быстро оборачивать небольшой пул (короткие транзакции + очередь).","Общий предел: (число экземпляров приложения × размер пула) < max_connections БД; оставьте запас для соединений суперпользователя и обслуживания.","Осторожно: начинайте с формулы, но всегда измеряйте; настраивайте по метрикам времени ожидания, активных соединений и глубины очереди."],"related":["connection-storm"]},"sol_session_mode":{"kind":"solution","title":"Рекомендация: режим пула session","detail":"В режиме session клиентское соединение закрепляется за серверным соединением до окончания сессии. Состояние уровня сессии (SET, временные таблицы, advisory locks, подготовленные операторы сессии) сохраняется, но совместное использование соединений минимально.","steps":["Почему: это единственный безопасный режим для приложений, которым нужно сохранение состояния на всю сессию; поведение такое же, как при прямом подключении к БД.","Когда: приложения, использующие переменные сессии SET, временные таблицы, LISTEN/NOTIFY, подготовленные операторы уровня сессии или advisory locks.","Пример: PgBouncer pool_mode=session; в ProxySQL — отдельная маршрутизация трафика, чувствительного к сессии.","Осторожно: совместное использование соединений низкое, поэтому при большом числе клиентов выгода уменьшается; по возможности избавьте приложение от состояния сессии и перейдите на режим transaction.","Различия СУБД: PgBouncer для PostgreSQL; на стороне MySQL ProxySQL реализует подобное поведение иначе, а с переменными сессии нужна осторожность."],"related":["connection-storm"]},"sol_txn_mode":{"kind":"solution","title":"Рекомендация: режим пула transaction","detail":"В режиме transaction серверное соединение выдаётся клиенту только на время транзакции и возвращается в пул по её завершении. Так тысячи клиентов могут работать с очень небольшим числом реальных соединений с БД; это самый распространённый режим для высокого масштаба.","steps":["Почему: поскольку соединения передаются на каждую транзакцию, совместное использование максимально; вы обслуживаете много клиентов малым числом соединений с БД.","Когда: короткие независимые транзакции; микросервисные/многоподовые архитектуры, не хранящие состояния сессии (или приведённые к этому).","Пример: PgBouncer pool_mode=transaction (самая распространённая настройка), стандартное поведение мультиплексирования ProxySQL.","Осторожно: функции, охватывающие сессию, ломаются: подготовленные операторы уровня сессии, временные таблицы, SET и advisory locks небезопасны. Для именованных подготовленных операторов на уровне протокола проверьте совместимость приложения и пула.","Режим statement: ещё более агрессивный режим (statement) передаёт соединение на каждый оператор; рассматривается только для очень специфичных сценариев только для чтения без транзакций и сильно ограничивает."],"related":["connection-storm"]},"sol_serverless":{"kind":"solution","title":"Рекомендация: для serverless внешний пул обязателен","detail":"Serverless/Lambda-функции быстро масштабируются, и каждый экземпляр открывает собственное соединение; пул внутри приложения здесь не помогает, так как экземпляры короткоживущие и ничего не разделяют. Когда внезапно появляются сотни/тысячи экземпляров, лимит соединений БД превышается. Решение — внешний пул соединений перед БД.","steps":["Почему: в короткоживущих, не разделяемых экземплярах функций пул внутри приложения не сохраняется; при масштабировании возникает шторм соединений.","Когда: среды, создающие экземпляр на каждый запрос, такие как AWS Lambda, Cloud Functions, Azure Functions.","Пример: PgBouncer/ProxySQL как отдельный уровень или управляемый пул (например, AWS RDS Proxy, Data API/внешние сервисы пулинга для PostgreSQL).","Режим: обычно предпочтителен режим пула transaction; освободите код функций от состояния сессии.","Осторожно: также задайте лимит параллелизма на функцию (reserved concurrency); определяйте размер пула и max_connections БД совместно, иначе узким местом станет сам пул."],"related":["connection-storm"]}}},"solver/decide-cache.json":{"id":"decide-cache","title":"Решение: какая стратегия кэширования?","icon":"🧭","category":"Руководство по выбору","intro":"Кэш хранит часто читаемые данные в быстром слое перед базой данных (например, Redis), снижая задержку и нагрузку на БД. Но не все данные подходят для кэширования, а неверная инвалидация создаёт риск устаревших/неверных данных. Это дерево сначала проверяет, действительно ли нужен кэш, затем помогает выбрать между cache-aside, read-through, write-through и write-behind по паттерну чтения/записи и рассматривает предотвращение stampede.","start":"root","nodes":{"root":{"kind":"step","title":"Как выглядит ваша нагрузка: преобладает чтение или запись?","detail":"Кэш окупается больше всего, когда одни и те же данные читаются много раз (преобладание чтения) и меняются нечасто. Сначала определите общий паттерн: данные много читаются и редко меняются, постоянно обновляются, или вы не уверены, стоит ли добавлять кэш?","choices":[{"label":"Сильное преобладание чтения, данные меняются относительно редко","next":"read_pattern"},{"label":"Записи тоже много; записанные данные читаются сразу/часто","next":"write_pattern"},{"label":"Не уверен; действительно ли мне нужен кэш?","next":"sol_no_cache"}]},"read_pattern":{"kind":"step","title":"Кто должен отвечать за наполнение кэша?","detail":"В сценарии с преобладанием чтения ключевой выбор — кто наполняет кэш. Должно ли приложение явно управлять кэшем и само обращаться к БД (высокая гибкость), или уровень кэша должен при промахе получать данные из базовой БД и прозрачно их возвращать?","choices":[{"label":"Пусть приложение управляет явно; мне нужны гибкость и контроль","next":"sol_cache_aside"},{"label":"Пусть уровень кэша получает данные из БД при промахе (прозрачно)","next":"sol_read_through"}]},"write_pattern":{"kind":"step","title":"При записи приоритет — согласованность или скорость записи?","detail":"Если записи много, нужно решить, насколько согласованными будут записанные данные между кэшем и БД. Обновлять ли кэш и БД вместе при каждой записи для гарантии согласованности (запись несколько медленнее), или запись сначала идёт в кэш и асинхронно применяется к БД в фоне (очень быстрая запись, но риск потерь)?","choices":[{"label":"Согласованность важна; чтение всегда должно быть актуальным","next":"sol_write_through"},{"label":"Скорость записи критична; немного отложенная согласованность и риск потерь допустимы","next":"sol_write_behind"}]},"sol_no_cache":{"kind":"solution","title":"Рекомендация: пока не добавляйте кэш","detail":"Кэш не бесплатен: он вносит проблему согласованности (устаревшие данные), дополнительную инфраструктуру и сложность инвалидации. Если запрос и так быстрый или данные меняются при каждом запросе, кэш не даёт выгоды, а лишь добавляет поверхность отказов.","steps":["Почему: кэш добавляет накладные расходы на согласованность, инвалидацию и эксплуатацию. Без отдачи это чистый убыток.","Когда он не нужен: запрос уже быстрый благодаря индексу, данные различаются при каждом чтении/специфичны для пользователя и не используются повторно, или записей намного больше, чем чтений.","Вместо этого: сначала оптимизация индексов/запросов, материализованное представление или собственный буферный кэш/кэш планов БД.","Осторожно: не добавляйте кэш «на всякий случай»; не добавляйте его без измеренного узкого места чтения и повторно используемых данных.","Далее: когда реальная горячая точка чтения доказана, начните с cache-aside."],"related":["slow-query","high-cpu"]},"sol_cache_aside":{"kind":"solution","title":"Рекомендация: cache-aside (ленивая загрузка)","detail":"При cache-aside приложение сначала проверяет кэш; если данных нет (промах), оно читает их из БД, записывает в кэш и возвращает. Это самый распространённый и гибкий паттерн; кэш наполняется только реально запрашиваемыми данными.","steps":["Почему: простой, гибкий и устойчивый; даже при отказе кэша приложение продолжает работать, обращаясь к БД. Кэшируются только запрошенные данные.","Когда: ускорение чтения общего назначения; много чтения, умеренная запись; классическое использование с Redis/Memcached.","Инвалидация: при записи обновите или удалите связанный ключ (write-invalidate); ограничьте устаревание, задав каждому ключу разумный TTL.","Пример: Redis GET → если нет, запрос к БД → SET key value EX <ttl>.","Осторожно: первый запрос всегда даёт промах (холодный старт). Если один и тот же ключ одновременно запрашивают многие клиенты, возможен cache stampede — см. предотвращение stampede (следующий пункт).","Stampede: когда истекает популярный ключ, тысячи запросов одновременно обрушиваются на БД; предотвращайте это блокировкой/single-flight, ранним обновлением или TTL со случайным разбросом (jitter)."],"related":["high-cpu","slow-query"]},"sol_read_through":{"kind":"solution","title":"Рекомендация: кэш read-through","detail":"При read-through приложение взаимодействует только с кэшем; при промахе уровень кэша сам получает данные из БД и наполняет кэш (через provider/loader). Код приложения упрощается, а логика наполнения находится в одном месте.","steps":["Почему: логика наполнения централизована в уровне кэша; код приложения избавлен от необходимости «обрабатывать промах везде».","Когда: нагрузки с преобладанием чтения, где можно использовать единый уровень доступа к кэшу (библиотеку/сервис).","Пример: библиотека/клиент кэша (например, локальный кэш с заданным loader или уровень доступа перед Redis) загружает данные из БД при промахе.","Инвалидация: TTL и инвалидация при записи по-прежнему нужны; read-through сам по себе не гарантирует актуальность.","Осторожно: очень похож на cache-aside; разница в том, «кто наполняет». Риск stampede есть и здесь; применяйте те же контрмеры.","Примечание о СУБД: Redis сам по себе не выполняет read-through; этот паттерн обеспечивает уровень приложения/библиотеки."],"related":["slow-query"]},"sol_write_through":{"kind":"solution","title":"Рекомендация: кэш write-through","detail":"При write-through каждая запись идёт и в кэш, и в БД (синхронно); оба обновляются вместе. Поэтому кэш никогда не отстаёт от БД, а чтение всегда видит актуальные данные.","steps":["Почему: кэш и БД остаются согласованными; если записанные данные сразу читаются, риска устаревшего чтения нет.","Когда: сценарии, где записанные данные читаются сразу и часто и требуется строгая согласованность чтения.","Пример: при записи приложение сначала обновляет БД, а затем сразу устанавливает ключ кэша в новое значение (или удаляет его).","Осторожно: каждая запись идёт в два места, поэтому задержка записи растёт; в кэш могут без необходимости попадать данные, которые никогда не будут прочитаны. Две записи не атомарны (одна успешна, другая — нет), что может вызвать несогласованность — при ошибке удаляйте запись кэша.","Ловушка двойной записи: вместо раздельной записи в БД и кэш обычно безопаснее подход «записать в БД, удалить (инвалидировать) кэш»."],"related":[]},"sol_write_behind":{"kind":"solution","title":"Рекомендация: кэш write-behind (write-back)","detail":"При write-behind запись сначала идёт в кэш и сразу возвращается пользователю; запись в БД выполняется в фоне асинхронно (пакетами/с задержкой). Скорость записи максимальна, но есть риски для надёжности и согласованности.","steps":["Почему: задержка записи резко снижается, а записи можно эффективно передавать в БД пакетами; БД разгружается при нагрузке с интенсивной записью.","Когда: очень большие объёмы записи, такие как счётчики, метрики или ленты активности, где допустима немного отложенная согласованность.","Пример: запись в Redis → очередь/фоновый обработчик периодически сбрасывает данные в БД пакетами.","Осторожно: если узел кэша упадёт до записи в БД, данные будут ПОТЕРЯНЫ; не используйте для критичных данных, требующих надёжности, или защищайте надёжной очередью/AOF.","Осторожно: БД какое-то время остаётся устаревшей; другие системы, читающие БД напрямую, видят старые данные. Принимайте задержку согласованности и окно потерь осознанно."],"related":[]}}},"solver/decide-datatype.json":{"id":"decide-datatype","title":"Решение: выбор правильного типа данных","icon":"🧭","category":"Руководство по выбору","intro":"Выбор типа данных — одно из самых дорогих для последующего исправления решений: неверный тип даёт неверные результаты (например, float для денег), лишнее место и медленные запросы. Это дерево ведёт к правильному типу для числовых, текстовых полей, даты/времени, ключа (PK), полуструктурированных (JSON) и категориальных (ENUM) полей и указывает на классические ловушки, такие как деньги и кодировки.","start":"root","nodes":{"root":{"kind":"step","title":"Какие данные вы храните?","detail":"Правильный тип определяется природой данных. К какой категории относится поле: число, текст, дата/время, первичный ключ таблицы, гибкие/полуструктурированные (JSON) данные или ограниченный набор категорий?","choices":[{"label":"Числовое значение (количество, деньги, измерение, счётчик)","next":"numeric"},{"label":"Текст (имя, описание, код)","next":"sol_text"},{"label":"Дата и/или время","next":"sol_datetime"},{"label":"Я проектирую первичный ключ (PK)","next":"sol_pk"},{"label":"Гибкие/вложенные/переменные поля (полуструктурированные данные)","next":"sol_json"},{"label":"Ограниченный известный набор категорий (статус, тип, приоритет)","next":"sol_enum"}]},"numeric":{"kind":"step","title":"Число целое или дробное — и это деньги?","detail":"Для чисел самое важное различие — точность. Значение целое (количество, ID, счётчик) или дробное? Если дробное, это ДЕНЬГИ/финансовая сумма? Двоичная плавающая точка (FLOAT/DOUBLE) никогда не используется для денег и везде, где требуется точность, так как даёт ошибки округления.","choices":[{"label":"Целое (количество, счётчик, id)","next":"sol_integer"},{"label":"Деньги или точное десятичное (цена, баланс, ставка)","next":"sol_decimal"}]},"sol_integer":{"kind":"solution","title":"Рекомендация: целое (SMALLINT / INT / BIGINT)","detail":"Для целых выбирайте наименьший тип, покрывающий нужный диапазон. Неоправданно большой тип тратит место и увеличивает размер индекса; но для растущих полей, таких как счётчики/PK, достаточная разрядность обязательна, чтобы избежать переполнения.","steps":["Почему: правильная разрядность оптимизирует и хранение, и объём индекса/памяти; арифметика быстрая.","Когда SMALLINT/INT: для ограниченного диапазона (возраст, количество, небольшой счётчик) достаточно SMALLINT/INT.","Когда BIGINT: выбирайте BIGINT для постоянно растущих ID/счётчиков и PK, число строк которых может достичь миллиардов, — переполнение INT является серьёзной причиной аварий в продуктиве.","Различия СУБД: в MySQL INT UNSIGNED удваивает диапазон; в PostgreSQL нет UNSIGNED, переходите на BIGINT. Oracle NUMBER(p), MSSQL INT/BIGINT.","Осторожно: не ставьте BIGINT везде «на случай роста»; но для растущего PK BIGINT с самого начала — дешёвая страховка."],"related":[]},"sol_decimal":{"kind":"solution","title":"Рекомендация: NUMERIC / DECIMAL — НИКОГДА не FLOAT для денег","detail":"Для денег и точных десятичных значений используйте NUMERIC/DECIMAL с фиксированной точкой (точные). FLOAT/DOUBLE — двоичная плавающая точка, они не могут точно хранить значения вроде 0.1; сложение/умножение даёт расхождения в копейках, неверные балансы и проблемы при аудите.","steps":["Почему: NUMERIC/DECIMAL хранят значения точно в десятичном виде; денежная арифметика даёт правильные результаты без ошибок округления.","Когда: любые поля, требующие точности, такие как цена, баланс, налог, ставка или процент. Задайте подходящий масштаб: DECIMAL(precision, scale), например DECIMAL(19,4) для суммы.","Альтернатива: некоторые системы хранят деньги в наименьшей единице (центах) как BIGINT; это тоже даёт полную точность.","Различия СУБД: PostgreSQL NUMERIC, MySQL DECIMAL, Oracle NUMBER(p,s), MSSQL DECIMAL/MONEY (учитывайте ограничения MONEY).","Осторожно: FLOAT/DOUBLE — только для научных/измеренных приближённых значений и случаев, где производительность важнее точности, — никогда для денег."],"related":[]},"sol_text":{"kind":"solution","title":"Рекомендация: текст — VARCHAR vs TEXT vs CHAR","detail":"Для текста используют VARCHAR/TEXT при переменной длине и CHAR при действительно фиксированной длине. В современных СУБД разница в хранении между VARCHAR и TEXT в основном пренебрежимо мала; реальное решение — вводить ли смысловое ограничение (ограничение длины).","steps":["VARCHAR(n): если длина переменная и есть разумный верхний предел (например, email, имя), используйте VARCHAR(n) — предел служит инструментом проверки данных.","TEXT: для длинного свободного текста непредсказуемой длины (описание, тело) используйте TEXT.","CHAR(n): используйте только для действительно фиксированной длины (например, код страны 'TR', всегда 2 символа); для короткого текста CHAR дополняет пробелами в конце и обычно не нужен.","Различия СУБД: в PostgreSQL VARCHAR и TEXT работают одинаково, предел — лишь ограничение. В MySQL VARCHAR хранится в строке, TEXT может храниться отдельно, а для индексации действует ограничение префикса. В Oracle используйте VARCHAR2.","Кодировка/collation: для эмодзи и полного Unicode в MySQL всегда используйте utf8mb4 (старая 'utf8' — 3 байта и ломает эмодзи); выбирайте collation (например, utf8mb4_0900_ai_ci) в соответствии с потребностями сортировки/сравнения. В PostgreSQL обычно — база данных UTF-8 + подходящий collation."],"related":[]},"sol_datetime":{"kind":"solution","title":"Рекомендация: дата/время — TIMESTAMP с часовым поясом","detail":"Если вы храните момент времени (время события), выбирайте тип с учётом часового пояса. Хранение без часового пояса ведёт к неверным вычислениям и сравнениям между регионами/при переходе на летнее время.","steps":["Почему: тип с учётом часового пояса нормализует момент к UTC и обеспечивает согласованное сравнение и преобразование; обязателен для глобальных систем.","Когда TIMESTAMPTZ: для моментов событий/транзакций в PostgreSQL используйте TIMESTAMP WITH TIME ZONE (timestamptz); храните UTC и преобразуйте в часовой пояс пользователя при отображении.","Только дата или время: DATE для календарного дня, TIME для времени, не связанного с часовыми поясами; INTERVAL для длительностей (в СУБД, которые его поддерживают).","Различия СУБД: PostgreSQL timestamptz (фактически хранит UTC). В MySQL TIMESTAMP преобразуется в соответствии с часовым поясом, но его диапазон ограничен (2038); DATETIME не преобразуется — соблюдайте дисциплину хранения в UTC в приложении. Oracle TIMESTAMP WITH TIME ZONE, MSSQL DATETIMEOFFSET.","Осторожно: хранение времени как текста (VARCHAR) или как epoch INT ломает сортировку, диапазоны и операции с часовыми поясами; используйте настоящий тип даты/времени."],"related":[]},"sol_pk":{"kind":"solution","title":"Рекомендация: первичный ключ — BIGINT vs UUID","detail":"Для PK есть два основных варианта: возрастающее число (BIGINT identity/sequence) или UUID. Числовой PK мал и удобен для индекса, но предсказуем и требует централизованной генерации; UUID можно генерировать распределённо, но он больше, а его случайный порядок может вредить индексу.","steps":["BIGINT (возрастающий): наименьший и самый быстрый индекс; последовательные вставки идеальны для B-tree. Выбор по умолчанию для одной БД/централизованной генерации; BIGINT вместо INT с самого начала защищает от переполнения.","UUID: можно генерировать на клиентах/в распределённых системах без коллизий, и он не раскрывает порядковый номер, когда ID виден снаружи. Но занимает 16 байт (2×BIGINT).","Порядок UUID важен: полностью случайные UUID (v4) не последовательны, поэтому вызывают разрозненные вставки в индекс (расщепление страниц, низкий коэффициент попаданий в кэш); упорядоченные по времени UUID (v7/ULID) в основном устраняют это.","Различия СУБД: в PostgreSQL есть встроенный тип uuid; в MySQL храните UUID как BINARY(16) (а не как текст CHAR(36)) и по возможности генерируйте упорядоченно. MSSQL uniqueidentifier + NEWSEQUENTIALID, Oracle RAW(16).","Осторожно: хранение UUID как текста VARCHAR/CHAR(36) примерно в 2-3 раза увеличивает занимаемое место и размер индекса; храните его в двоичном виде."],"related":[]},"sol_json":{"kind":"solution","title":"Рекомендация: JSON / JSONB — используйте умеренно","detail":"Тип JSON удобен для полей с гибкой схемой, вложенных или разреженных полей; но реляционная дисциплина (ограничения, индексы, типобезопасность) может быть утрачена. Правило: выносите постоянные и часто запрашиваемые поля в обычные столбцы, в JSON держите только действительно изменчивую часть.","steps":["Почему: гибко хранит изменчивые/необязательные поля в одном столбце; поля можно добавлять без миграции схемы.","Когда: непредсказуемые структуры, такие как полезная нагрузка внешних API, пользовательские поля или атрибуты товаров.","Различия СУБД: в PostgreSQL используйте JSONB (двоичный, индексируемый, запросы @>/? с GIN) — предпочитайте его обычному JSON. Тип JSON в MySQL тоже двоичный, запросы к нему выполняются через generated column + индекс. В Oracle 21c+ есть тип JSON/OSON; MSSQL до 2025 хранит JSON в NVARCHAR и обрабатывает функциями; в SQL Server 2025 и Azure SQL есть встроенный (двоичный) тип json.","Осторожно: индексируйте часто фильтруемые поля JSON (PostgreSQL GIN, индекс по generated column в MySQL); иначе каждый запрос сканирует весь документ.","Осторожно: не запихивайте всё в JSON — теряются реляционная целостность, внешние ключи и типизация. Поля, требующие запросов/ограничений, должны быть настоящими столбцами."],"related":["decide-sqlnosql"]},"sol_enum":{"kind":"solution","title":"Рекомендация: ENUM vs справочная (lookup) таблица","detail":"Для ограниченных наборов категорий есть два варианта: тип ENUM СУБД или справочная таблица + внешний ключ. ENUM компактен и прост, но добавление значения требует изменения схемы; справочная таблица гибка и может нести дополнительные атрибуты (описание, флаг активности, порядок).","steps":["Когда ENUM: если значения очень стабильны и редко меняются (например, пол, день недели) и дополнительные атрибуты не нужны; компактно и читаемо.","Когда справочная таблица: если значения могут меняться со временем, нужны дополнительные поля, такие как переводы/описание/порядок/флаг активности, или отчёты выполняют по ней соединения, — она гибче.","Различия СУБД: PostgreSQL и MySQL предлагают встроенный тип ENUM (в PostgreSQL добавление значения в ENUM требует ALTER TYPE, а порядок следует порядку определения). В Oracle и MSSQL встроенного ENUM нет; используйте ограничение CHECK или справочную таблицу.","Осторожно: добавление/удаление значения ENUM — это миграция схемы, которая может вызвать блокировки/затраты на большой таблице; для часто меняющихся наборов предпочитайте справочную таблицу.","Альтернатива: свободный текст + ограничение CHECK — тоже практичный компромисс для небольших стабильных наборов."],"related":[]}}},"solver/decide-capacity.json":{"id":"decide-capacity","title":"Решение: определение размера сервера","icon":"🧭","category":"Руководство по выбору","intro":"Правильное определение размера начинается с профиля нагрузки (OLTP, хранилище данных, смешанная) и требует найти реальное узкое место (CPU, ввод-вывод или RAM). Золотое правило: часто используемый набор данных (working set) должен помещаться в RAM; сначала измеряйте, затем масштабируйте и оставляйте запас на внезапную нагрузку. Это дерево помогает определить нагрузку и выбрать ресурс и направление масштабирования (вертикальное/горизонтальное) по узкому месту.","start":"root","nodes":{"root":{"kind":"step","title":"Каков профиль вашей нагрузки?","detail":"Определение размера начинается с нагрузки, потому что OLTP и аналитическая нагрузка требуют совершенно разных профилей ресурсов. Что преобладает в работе системы: множество небольших быстрых транзакций (OLTP), отчётность/аналитика с большими сканированиями и агрегациями (DWH) или смесь того и другого? Если не уверены, сначала нужно измерить.","choices":[{"label":"OLTP — множество небольших коротких транзакций (веб/приложение)","next":"bottleneck"},{"label":"Аналитика / хранилище данных (DWH) — большие сканирования и агрегации","next":"sol_dwh"},{"label":"Смешанная нагрузка или точно не знаю","next":"sol_measure"},{"label":"Я знаю узкое место; масштабировать вертикально или горизонтально?","next":"sol_scale"}]},"bottleneck":{"kind":"step","title":"Где узкое место: RAM, ввод-вывод или CPU?","detail":"В OLTP найдите реальное узкое место вместо того, чтобы вслепую добавлять ресурсы. Посмотрите на метрики: часто используемые данные не помещаются в RAM и много чтений с диска (RAM/ввод-вывод), недостаточны задержка/IOPS диска (ввод-вывод), или данные уже в памяти, но CPU загружен (CPU)? Масштабирование не того ресурса тратит деньги и время.","choices":[{"label":"Часто используемые данные не помещаются в RAM; много чтений с диска","next":"sol_ram"},{"label":"Данные в памяти, но задержка/IOPS диска недостаточны (интенсивная запись)","next":"sol_io"},{"label":"Данные в памяти и с диском всё в порядке, но CPU постоянно загружен","next":"sol_cpu"}]},"sol_measure":{"kind":"solution","title":"Рекомендация: сначала измерьте, затем определяйте размер","detail":"Покупка оборудования на основе догадок почти всегда даёт неверный размер. Профиль нагрузки и узкое место выявляются из реальных метрик. Определение размера — не догадка, а итерации на основе измерений.","steps":["Почему: OLTP это или DWH и где узкое место — в CPU, вводе-выводе или RAM, становится ясно только при измерении под реальной нагрузкой; догадки — дорогая ошибка.","Измеряйте: загрузку CPU и очередь выполнения (run queue), коэффициент попаданий в память/кэш (buffer cache hit), IOPS и задержку диска, активные соединения и события ожидания.","Инструменты СУБД: PostgreSQL pg_stat_* и события ожидания, Oracle AWR/ASH, MySQL Performance Schema, MSSQL DMV и wait stats.","Подход: протестируйте с репрезентативной нагрузкой, найдите самый дефицитный ресурс, масштабируйте только его и измерьте снова. Меняйте по одному ресурсу за раз, чтобы изолировать эффект.","Далее: когда профиль ясен, переходите к соответствующему решению (узкое место OLTP, DWH или горизонтальное/вертикальное масштабирование)."],"related":["high-cpu","memory-pressure","slow-query"]},"sol_ram":{"kind":"solution","title":"Рекомендация: добавьте RAM — рабочий набор (working set) должен помещаться в память","detail":"Самое важное правило OLTP: часто используемые данные и индексы (working set) должны помещаться в память (буфер/кэш). Если нет, каждый запрос идёт на диск, задержка резко растёт, а ввод-вывод становится узким местом. RAM обычно — вложение с наибольшей отдачей.","steps":["Почему: чтение из памяти в сотни-тысячи раз быстрее, чем с диска; когда working set помещается в RAM, коэффициент попаданий в кэш растёт, а дисковый ввод-вывод резко падает.","Когда: низкий коэффициент попаданий в кэш, частые события ожидания «чтение с диска», активный набор данных больше доступной RAM.","Как: правильно задайте размер области памяти БД — PostgreSQL shared_buffers + страничный кэш ОС, MySQL innodb_buffer_pool_size (обычно большая часть RAM), Oracle SGA/buffer cache, MSSQL max server memory.","Цель: в идеале working set + индексы помещаются в память; если всё не помещается, то хотя бы наиболее часто используемая «горячая» часть.","Осторожно: не отдавайте всю физическую RAM под buffer pool; оставьте место для ОС, памяти соединений и других процессов, иначе свопинг замедлит всё."],"related":["memory-pressure","slow-query"]},"sol_io":{"kind":"solution","title":"Рекомендация: усильте уровень ввода-вывода (IOPS/задержка)","detail":"Даже когда данные в памяти, при нагрузке с интенсивной записью решающей является скорость диска (commit, WAL/redo/binlog, checkpoint). Проблема здесь не в ёмкости, а в IOPS и задержке. Неверный класс дисков ставит систему на колени, несмотря на быстрые CPU/RAM.","steps":["Почему: ради надёжности каждый commit записывает журнал на диск; диск с низкими IOPS/высокой задержкой напрямую ограничивает пропускную способность транзакций.","Когда: высокая задержка диска, долгие ожидания «запись журнала/WAL», узкое место во время checkpoint, OLTP с интенсивной записью.","Как: используйте NVMe/SSD; вынесите журнал транзакций на быстрый диск, отдельный от файлов данных; в облаке выбирайте класс дисков с достаточными выделенными IOPS/пропускной способностью (например, provisioned IOPS).","Цель: задайте целевые значения и для IOPS, и для задержки — важны не только IOPS, но и задержка p99. RAID/кэш записи (BBU) может снизить задержку записи.","Осторожно: в облаке пропускная способность диска и IOPS часто зависят и от размера инстанса; задержка сетевых дисков выше, чем у локальных NVMe."],"related":["disk-full","slow-query"]},"sol_cpu":{"kind":"solution","title":"Рекомендация: устраните узкое место CPU (сначала запросы, затем ядра)","detail":"Если CPU загружен, когда данные в памяти и с диском всё в порядке, первопричина обычно — неэффективные запросы (плохие планы, недостающие индексы, множество мелких запросов) или избыточный параллелизм. Снизить нагрузку до добавления ядер обычно дешевле и эффективнее.","steps":["Почему: CPU больше всего потребляют полные сканирования, сортировки/хеширование, компиляция планов и переключения контекста из-за слишком большого числа соединений. Исправить это дешевле, чем добавлять ядра.","Сначала оптимизируйте: найдите самые дорогие запросы (pg_stat_statements / AWR / Performance Schema), добавьте недостающие индексы, сократите N+1 запросы, уменьшите пул соединений.","Затем масштабируйте: если запросы в порядке, а CPU всё ещё загружен, увеличьте число ядер (вертикально) — особенно для OLTP с высоким параллелизмом.","Примечание о СУБД: больше соединений = больше переключений контекста; небольшой пул с быстрой оборачиваемостью часто эффективнее большего числа ядер (см. решение о пуле соединений).","Осторожно: если лицензирование по ядрам (Oracle/MSSQL), добавление ядер дорого; оптимизация запросов в первую очередь снижает затраты."],"related":["high-cpu","slow-query"]},"sol_dwh":{"kind":"solution","title":"Рекомендация: аналитика/DWH — пропускная способность, параллелизм и горизонтальное масштабирование","detail":"Нагрузка хранилища данных состоит из небольшого числа тяжёлых запросов, сканирующих и агрегирующих большие таблицы. Приоритет здесь — не низкая задержка, а высокая пропускная способность сканирования, параллелизм и обычно горизонтально масштабируемая архитектура.","steps":["Почему: запросы сканируют терабайты; решающими являются пропускная способность диска/памяти и параллельно работающие ядра, а не задержка отдельных транзакций.","Как: хранилище с высокой пропускной способностью ввода-вывода, много RAM (для агрегации/hash join), много ядер и параллельные запросы; колоночное хранилище или архитектура MPP очень эффективны в большом масштабе.","Горизонтальное масштабирование: для большого DWH распределённая/MPP-архитектура (например, аналитические движки с шардингом) обычно масштабируется лучше, чем наращивание одной машины; секционирование + partition pruning сокращают сканирование.","Примечание о СУБД: не переносите настройки OLTP — для DWH предпочтительны больший work_mem/области сортировки, более высокая степень параллелизма и большие блоки/readahead.","Осторожно: не смешивайте OLTP и DWH на одном сервере; тяжёлые отчёты душат OLTP. По возможности вынесите аналитику на отдельную реплику/хранилище данных."],"related":["decide-shard-replica","decide-partition"]},"sol_scale":{"kind":"solution","title":"Общий принцип: вертикальное vs горизонтальное + запас + сначала измерения","detail":"Способ масштабирования важен так же, как и выбор ресурса. Вертикальное (более мощная машина) — просто, но есть потолок и единая точка отказа; горизонтальное (больше машин) масштабируется, но добавляет сложности. В любом случае оставляйте запас на внезапную нагрузку и подтверждайте решение измерениями.","steps":["Вертикальное (scale-up): увеличьте ресурсы (CPU/RAM/IO) на одной машине — самый простой вариант и первый шаг для большинства OLTP; но есть физический/ценовой потолок и единая точка отказа.","Горизонтальное (scale-out): добавьте реплики для чтения (для нагрузки чтения) или шардинг (для записи/объёма данных) — почти неограниченный масштаб, но приносит сложность согласованности и эксплуатации.","Запас: не планируйте ресурсы на 100%; оставляйте ~30% запаса при обычной пиковой нагрузке, чтобы уместить внезапные всплески, обслуживание и рост.","Сначала измерения: перед масштабированием подтвердите узкое место измерениями, нацельтесь на самый дефицитный ресурс и измерьте снова после изменения.","Осторожно: горизонтальное масштабирование решает не все проблемы — распределённые транзакции и соединения дороги; переходите, только когда вертикальное масштабирование и оптимизация исчерпаны."],"related":["decide-shard-replica","decide-ha"]}}},"solver/slow-write.json":{"id":"slow-write","title":"Медленная запись (задержка INSERT/UPDATE)","icon":"🐢","category":"Производительность","intro":"Операции INSERT/UPDATE/DELETE медленнее ожидаемого. Сначала определим, чего ждёт запись (commit/redo, блокировки, диск или накладные расходы на строку), затем пошагово дойдём до первопричины.","start":"root","nodes":{"root":{"kind":"step","title":"Чего ждёт запись?","detail":"Классифицируйте узкое место по событиям ожидания: преобладает синхронизация commit/redo (WAL), блокировки, задержка записи на диск или накладные расходы на индексы/триггеры для каждой строки?","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":"Ожидание commit / redo (синхронизация WAL)","next":"commit"},{"label":"Ожидание блокировки","next":"lock"},{"label":"Подозрение на накладные расходы на строку (индексы/триггеры)","next":"overhead"},{"label":"Высокая задержка записи на диск","next":"sol_disk"}]},"commit":{"kind":"step","title":"Паттерн commit или fsync?","detail":"Отдельный commit на каждую строку (autocommit) вызывает синхронизацию redo/журнала столько раз, сколько строк. Напротив, если число commit разумно, но каждый commit ждёт fsync, проблема в задержке диска журнала.","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 / commit на каждой строке","next":"sol_batch"},{"label":"Commit ждёт синхронизации журнала (fsync)","next":"sol_disk"}]},"lock":{"kind":"step","title":"Кто удерживает блокировку?","detail":"Если запись ждёт блокировку: другая сессия может удерживать ту же строку/индекс, или множество сессий пытаются писать в одну и ту же «горячую» строку.","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":"Блокировку удерживает другая сессия","next":"sol_lock"},{"label":"Много сессий пишут в одну горячую строку","next":"sol_hotrow"}]},"overhead":{"kind":"step","title":"Откуда берутся накладные расходы на строку?","detail":"Каждый INSERT/UPDATE также обновляет все индексы таблицы и запускает триггеры/проверку ограничений. Избыточное число индексов или тяжёлый триггер значительно замедляют запись.","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":"У таблицы слишком много / ненужные индексы","next":"sol_index"},{"label":"Есть затраты на тяжёлый триггер или ограничение","next":"sol_trigger"}]},"sol_batch":{"kind":"solution","title":"Первопричина: autocommit / commit на каждую строку","detail":"Отдельный commit для каждой строки повторяет синхронизацию redo/журнала столько раз, сколько строк. Группируя операции, можно резко сократить число commit (а значит, и fsync).","steps":["Отключите autocommit в приложении и применяйте пакетные commit, фиксируя каждые N строк (например, 500-5000).","Используйте многострочные INSERT / пути массовой загрузки вместо однострочных INSERT (Postgres COPY, Oracle bulk insert, MySQL LOAD DATA, MSSQL BULK INSERT).","Используйте подготовленные операторы, чтобы снизить затраты на повторный разбор (hard parse).","После изменения убедитесь, что ожидание 'log file sync' / WRITELOG снизилось."],"related":["active-sessions"]},"sol_disk":{"kind":"solution","title":"Первопричина: медленный диск redo/журнала (задержка fsync)","detail":"Каждый commit ждёт, пока запись redo/WAL не будет записана на надёжный носитель (fsync). Если диск журнала медленный (высокая задержка записи, недостаточно IOPS), каждый commit платит эту задержку.","steps":["Вынесите файлы журнала/redo на выделенный диск с низкой задержкой (желательно NVMe SSD); физически отделите их от файлов данных.","Измерьте задержку хранилища (статистика файлового ввода-вывода); если мс на запись высоки, проверьте оборудование/кэш записи RAID.","В виртуальных/облачных средах повысьте выделенные IOPS и лимиты пропускной способности.","Если требования к надёжности позволяют, рассмотрите настройки group commit / задержки commit (например, Postgres commit_delay); но взвесьте риск потери данных."],"related":[]},"sol_lock":{"kind":"solution","title":"Первопричина: блокировку удерживает другая сессия","detail":"Запись ждёт блокировку строки/индекса, удерживаемую другой сессией. Блокирующая сессия обычно — долгая транзакция или та, где забыли выполнить commit.","steps":["Определите блокирующую сессию и изучите, что она делает (длинный пакет или idle in transaction?).","Если блокирующая сессия без причины остаётся открытой/долгой, исправьте паттерн commit/rollback в приложении.","В экстренной ситуации контролируемо завершите блокирующую сессию (kill session / pg_terminate_backend / KILL).","Держите транзакции короткими; не ждите действий пользователя внутри транзакции."],"related":["blocking-sessions","current-locks"]},"sol_hotrow":{"kind":"solution","title":"Первопричина: конкуренция за горячую строку","detail":"Множество сессий пытаются писать в одну строку (например, счётчик, порядковый номер, баланс); все они встают в очередь за одной блокировкой, и запись сериализуется.","steps":["Для паттерна горячего счётчика разделите значение на параллельные корзины (sharded counter) и суммируйте их при чтении.","Перенесите генерацию порядковых номеров/ID с центральной единственной строки на механизм sequence/identity.","Снизьте частоту обновлений: накапливайте промежуточные итоги в приложении и записывайте их периодически.","Снизьте конкуренцию, стандартизировав порядок операций и сократив время удержания блокировок."],"related":["current-locks"]},"sol_index":{"kind":"solution","title":"Первопричина: избыточные накладные расходы на индексы","detail":"Каждый индекс таблицы обновляется при каждой записи. Множество или неиспользуемые индексы напрямую замедляют INSERT/UPDATE.","steps":["Определите неиспользуемые/перекрывающиеся индексы (статистика использования) и безопасно удалите их.","Упростите широкие составные индексы; объедините индексы с одинаковыми ведущими столбцами.","Для больших массовых загрузок рассмотрите временное отключение индексов и их перестроение после загрузки.","После изменения снова измерьте задержку записи и подтвердите улучшение."],"related":["slow-queries"]},"sol_trigger":{"kind":"solution","title":"Первопричина: тяжёлый триггер / ограничение","detail":"Триггер, срабатывающий при каждой записи, или проверяемое ограничение (особенно проверка внешнего ключа без индекса) добавляют дополнительные затраты к каждой строке.","steps":["Сделайте тяжёлую работу в теле триггера (например, синхронную запись в журнал) асинхронной/на основе очереди.","Проиндексируйте ссылающиеся (дочерние) столбцы внешних ключей; иначе каждый DELETE/UPDATE в родительской таблице полностью сканирует дочернюю.","По возможности переведите триггеры уровня строки на уровень оператора.","Перенесите ненужную логику триггеров или логику, ставшую бизнес-правилами, на уровень приложения."],"related":[]}}},"solver/io-bottleneck.json":{"id":"io-bottleneck","title":"Высокое ожидание IO / задержка диска","icon":"💽","category":"Производительность","intro":"Система ждёт дисковый ввод-вывод. Сначала определим, преобладает чтение или запись, затем дойдём до причин: недостаточный буферный кэш, полные сканирования таблиц, шторм checkpoint, лимиты IOPS или вытеснение во временное пространство.","start":"root","nodes":{"root":{"kind":"step","title":"IO преимущественно на чтение или на запись?","detail":"По статистике файлового ввода-вывода определите, приходится ли ожидание на чтение (данные читаются с диска) или на запись (redo/checkpoint/запись данных). Это делит дерево на две ветви.","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":"В основном IO на чтение","next":"read"},{"label":"В основном IO на запись","next":"write"}]},"read":{"kind":"step","title":"Почему высокая нагрузка IO на чтение?","detail":"Избыточные физические чтения имеют три основные причины: буферный кэш слишком мал (working set не помещается в RAM), запросы выполняют полные сканирования таблиц, или большие операции сортировки/хеширования вытесняются во временное пространство.","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":"Попадания в буферный кэш низкие, working set не помещается","next":"sol_cache"},{"label":"Запросы выполняют полные сканирования таблиц","next":"sol_scan"},{"label":"Большие сортировки/хеширование вытесняются во временное пространство","next":"sol_spill"}]},"write":{"kind":"step","title":"Почему IO на запись высокий?","detail":"Узкое место на стороне записи возникает либо из-за периодических checkpoint/сбросов, кратковременно перегружающих диск (шторм checkpoint), либо из-за нехватки IOPS/пропускной способности хранилища для устойчивой нагрузки.","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":"Внезапные волны checkpoint/сброса (шторм checkpoint)","next":"sol_checkpoint"},{"label":"Хранилище не справляется с устойчивой нагрузкой (лимит IOPS)","next":"sol_iops"}]},"sol_cache":{"kind":"solution","title":"Первопричина: недостаточный буферный кэш","detail":"Часто используемый набор данных (working set) не помещается в буферный кэш, поэтому блоки постоянно перечитываются с диска. Коэффициент попаданий низкий, физических чтений много.","steps":["Увеличьте размер буфера/кэша: Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","Сравните коэффициент попаданий и физические чтения до и после изменения.","Если RAM физически недостаточно, добавьте память на сервер или сократите working set с помощью архивирования/секционирования.","Если реальное узкое место вызвано одним паттерном сканирования, перейдите на ветку сканирования в дереве попаданий в кэш."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Первопричина: полные сканирования таблиц","detail":"Запросы сканируют большие таблицы целиком, потому что нет подходящего индекса; это и вызывает много физических чтений, и засоряет кэш бесполезными блоками.","steps":["Получите планы (EXPLAIN) запросов с наибольшим числом физических чтений и найдите полные сканирования.","Добавьте подходящие индексы на столбцы фильтров и соединений; убедитесь, что план перешёл на доступ по индексу.","Для очень больших таблиц сократите объём сканируемых данных с помощью секционирования по дате/диапазону.","Выбирайте только нужные столбцы вместо ненужного 'SELECT *', чтобы сократить число читаемых блоков."],"related":["slow-queries"]},"sol_spill":{"kind":"solution","title":"Первопричина: сортировки/хеширование вытесняются во временное пространство","detail":"Большие операции sort, hash join или group by не помещаются в рабочую память и пишут во временное пространство (temp/tempdb), создавая интенсивный ввод-вывод на чтение/запись.","steps":["Увеличьте параметр рабочей памяти: Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size/join_buffer_size, MSSQL memory grant.","Сократите объём операций: применяйте фильтры раньше, уберите ненужные сортировки, улучшите порядок соединений.","Предотвратите вытеснение на диск, добавив подходящие индексы для часто используемых сортировок/группировок.","Перенесите временное пространство на быстрый диск; если проблема сохраняется, углубитесь с помощью дерева давления на undo/temp."],"related":["slow-queries"]},"sol_checkpoint":{"kind":"solution","title":"Первопричина: шторм checkpoint","detail":"Грязные страницы долго накапливаются и массово записываются на диск через короткие интервалы; эти внезапные волны записи периодически перегружают IO.","steps":["Распределите checkpoint во времени: в Postgres увеличьте checkpoint_completion_target и сбалансируйте checkpoint_timeout.","В MySQL задайте innodb_io_capacity / innodb_io_capacity_max в соответствии с хранилищем и настройте adaptive flushing.","В MSSQL сгладьте внезапные волны с помощью косвенных checkpoint (target_recovery_time).","В Oracle сбалансируйте частоту checkpoint с помощью fast_start_mttr_target и увеличьте файлы redo log."],"related":[]},"sol_iops":{"kind":"solution","title":"Первопричина: недостаточные IOPS / пропускная способность","detail":"Хранилищу не хватает IOPS или пропускной способности для обслуживания запросов чтения+записи, требуемых нагрузкой; глубина очереди и задержка постоянно высоки.","steps":["Измерьте задержку диска (мс на запрос) и сравните её с лимитом IOPS оборудования/облака.","Перейдите на более быстрое хранилище (HDD→SSD/NVMe) или повысьте уровень выделенных IOPS/пропускной способности в облаке.","Снизьте конкуренцию, распределив данные, redo/журналы и временное пространство по разным дискам.","Для долгосрочного решения снизьте общую потребность в IO (индексирование, архивирование, оптимизация запросов)."],"related":[]}}},"solver/undo-temp-pressure.json":{"id":"undo-temp-pressure","title":"Давление на undo / TempDB / временное пространство","icon":"🧯","category":"Ёмкость","intro":"Пространство undo/rollback, временное (temp) пространство или TempDB под давлением. Определим, где давление, и дойдём до первопричины: большая сортировка/хеширование, долгий запрос/транзакция или утечка пространства.","start":"root","nodes":{"root":{"kind":"step","title":"Где давление: undo, temp или конкуренция в tempdb?","detail":"Классифицируйте источник давления по использованию пространства: заполняется табличное пространство undo/rollback, общее временное (temp) пространство или, в MSSQL, это конкуренция за latch при выделении страниц TempDB?","command":{"oracle":"SELECT tablespace_name, ROUND(used_percent,1) AS used_pct\nFROM dba_tablespace_usage_metrics\nWHERE tablespace_name LIKE '%UNDO%' OR tablespace_name LIKE 'TEMP%';","postgres":"SELECT datname, temp_files, pg_size_pretty(temp_bytes) AS temp_written\nFROM pg_stat_database ORDER BY temp_bytes DESC;","mysql":"SELECT * FROM information_schema.INNODB_TEMP_TABLE_INFO;\nSHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';","mssql":"SELECT SUM(user_object_reserved_page_count)*8/1024 AS user_obj_mb,\n       SUM(internal_object_reserved_page_count)*8/1024 AS internal_obj_mb,\n       SUM(version_store_reserved_page_count)*8/1024 AS version_store_mb\nFROM sys.dm_db_file_space_usage;"},"choices":[{"label":"Пространство undo/rollback заполняется (ORA-01555 / ORA-30036)","next":"undo"},{"label":"Временное (temp) пространство заполняется (вытеснение sort/hash)","next":"temp"},{"label":"Конкуренция за latch при выделении в TempDB (PFS/GAM/SGAM)","next":"tempdb"}]},"undo":{"kind":"step","title":"Что потребляет undo?","detail":"Давление на undo обычно бывает двух видов: долгому ЗАПРОСУ нужен старый undo для согласованного чтения на момент его начала (ORA-01555), или долгая/открытая ТРАНЗАКЦИЯ генерирует столько undo, что заполняет пространство и приводит к 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":"Долгому запросу нужен старый undo (ORA-01555)","next":"sol_undo_query"},{"label":"Долгая/открытая транзакция накапливает undo (ORA-30036)","next":"sol_undo_tx"}]},"temp":{"kind":"step","title":"Что заполняет временное пространство?","detail":"Давление на temp возникает либо из-за большой операции sort/hash, вытесняемой на диск (нормально, но огромно), либо из-за одной сессии, постоянно удерживающей temp (открытый курсор, утечка, бесконечный запрос).","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":"Большая операция sort/hash вытесняется на диск","next":"sol_temp_sort"},{"label":"Одна сессия удерживает temp (утечка/открытый курсор)","next":"sol_temp_leak"}]},"tempdb":{"kind":"step","title":"Конкуренция в TempDB связана с выделением или с объёмом?","detail":"В MSSQL TempDB возникают две разные проблемы: конкуренция PAGELATCH на страницах PFS/GAM/SGAM (множество мелких временных объектов) или большая сортировка/version store, заполняющие TempDB по объёму.","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":"Конкуренция за latch PFS/GAM/SGAM","next":"sol_tempdb_latch"},{"label":"Большая сортировка/version store заполняет TempDB","next":"sol_temp_sort"}]},"sol_undo_query":{"kind":"solution","title":"Первопричина: долгий запрос + недостаточный undo (ORA-01555)","detail":"Старый undo, нужный долгому запросу для согласованного чтения, был перезаписан, так как истёк срок хранения или не хватило пространства.","steps":["Повысьте undo_retention до значения, превышающего длительность самого долгого запроса (v$undostat.maxquerylen).","Увеличьте табличное пространство UNDO или включите autoextend, чтобы срок хранения был обеспечен пространством.","Для критичных отчётов защитите старый undo с помощью ALTER TABLESPACE undotbs RETENTION GUARANTEE (при заполнении пространства DML может завершаться ошибкой; будьте осторожны).","Для постоянного решения оптимизируйте долгий запрос или перенесите его на standby/реплику."],"related":["slow-queries"]},"sol_undo_tx":{"kind":"solution","title":"Первопричина: долгая/открытая транзакция заполнила undo (ORA-30036)","detail":"Один огромный DML или открытая транзакция с отложенным commit сгенерировали слишком много undo; табличное пространство UNDO не смогло расшириться, и возникла ORA-30036.","steps":["Определите активную транзакцию, удерживающую больше всего undo (v$transaction.used_ublk), и найдите её владельца.","Выполняйте один огромный DML частями по дате/диапазону с промежуточными commit.","Исправьте на стороне приложения сессии с забытым commit / idle-in-transaction; при необходимости контролируемо завершите их.","Временно добавьте пространство в табличное пространство UNDO; на постоянной основе планируйте срок хранения и размер пространства в соответствии с нагрузкой."],"related":["active-sessions"]},"sol_temp_sort":{"kind":"solution","title":"Первопричина: большая сортировка/хеширование заполняет временное пространство","detail":"Большие операции sort/hash/group by, не помещающиеся в память, вытесняются во временное пространство и могут его заполнить.","steps":["Увеличьте рабочую память: Oracle pga_aggregate_target, Postgres work_mem, MySQL sort_buffer_size, MSSQL memory grant.","Улучшите запрос: уберите ненужные сортировки, применяйте фильтры раньше, добавьте индексы на столбцы сортировки/группировки.","Разбивайте очень большие задания на части; ограничьте параллелизм, чтобы они не удерживали temp одновременно.","Увеличьте временное пространство и перенесите его на быстрый диск; настройте MSSQL TempDB с несколькими файлами в зависимости от числа ядер."],"related":["slow-queries"]},"sol_temp_leak":{"kind":"solution","title":"Первопричина: сессия удерживает временное пространство (утечка)","detail":"Одна сессия не освобождает временное пространство из-за незакрытых курсоров, бесконечного запроса или открытых временных таблиц.","steps":["Определите сессию, удерживающую больше всего temp, и выполняемый ею SQL.","Убедитесь, что курсоры/результирующие наборы и временные таблицы корректно закрываются в приложении.","Контролируемо завершите зависшую/чрезмерно долгую сессию и убедитесь, что пространство возвращено.","Определяйте глобальные временные таблицы так, чтобы они автоматически очищались по окончании сессии/транзакции."],"related":["active-sessions"]},"sol_tempdb_latch":{"kind":"solution","title":"Первопричина: конкуренция за latch PFS/GAM/SGAM в TempDB","detail":"Когда множество сессий одновременно создают мелкие временные объекты, на страницах карт выделения TempDB (PFS/GAM/SGAM) возникает конкуренция PAGELATCH.","steps":["Настройте число файлов данных TempDB пропорционально числу ядер CPU, с одинаковыми размерами (например, 4-8 файлов, все с одинаковым размером/autogrowth).","В современных SQL Server включите параметры, снижающие конкуренцию при выделении (memory-optimized tempdb metadata).","Сократите создание временных таблиц: замените ненужные таблицы #temp табличными переменными / одним запросом.","После изменения убедитесь, что время ожидания PAGELATCH снизилось."],"related":["current-locks"]}}},"solver/long-transaction.json":{"id":"long-transaction","title":"Долгая / открытая транзакция","icon":"⏱️","category":"Блокировки","intro":"Долгая или открытая транзакция накапливает блокировки, undo и раздувание (bloat). Найдём проблемную транзакцию, определим её тип (idle in transaction, длинный пакет, влияние) и безопасно устраним.","start":"root","nodes":{"root":{"kind":"step","title":"Какая открытая транзакция самая старая?","detail":"Выведите открытые транзакции по времени начала. Источник проблемы обычно — самая старая. Проверьте её состояние: она активно выполняется или находится в 'idle in transaction', ожидая 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 забыт, простаивает)","next":"idle"},{"label":"Активный, но очень долгий пакет/DML","next":"batch"},{"label":"Накапливает блокировки/bloat/undo, пока транзакция продолжается","next":"impact"}]},"idle":{"kind":"step","title":"Почему возникает idle in transaction?","detail":"Сессия открыла транзакцию, но простаивает, не выполняя запросов; то есть commit/rollback забыт. Это без необходимости удерживает блокировки и undo. Достаточно исправить приложение или нужно срочно завершить её?","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":"Приложение/пул соединений не выполняет commit (постоянное исправление)","next":"sol_idle"},{"label":"Мне нужно завершить её прямо сейчас","next":"sol_kill"}]},"batch":{"kind":"step","title":"Что делает активная долгая транзакция?","detail":"Сессия действительно выполняется (active). Это один огромный DML/отчёт или блокирующая сессия, которая заставляет ждать другие сессии и должна быть срочно прервана?","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":"Один огромный DML/отчёт; его нужно разбить","next":"sol_batch"},{"label":"Она заставляет ждать другие; срочно завершить","next":"sol_kill"}]},"impact":{"kind":"step","title":"Что накапливает долгая транзакция?","detail":"Определите побочный эффект долгой транзакции: это в основном очередь блокировок, заставляющая ждать другие сессии, или раздувание из-за удержания старых версий строк и блокировки очистки (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":"Очередь блокировок заставляет ждать другие сессии","next":"sol_lock_backlog"},{"label":"Накопление старых версий блокирует очистку (undo/bloat)","next":"sol_bloat"}]},"sol_idle":{"kind":"solution","title":"Первопричина: приложение не выполняет commit (idle in transaction)","detail":"Пул соединений или приложение открывает транзакцию, а затем держит соединение в простое без commit/rollback. Это без необходимости удерживает блокировки и undo.","steps":["В коде приложения гарантируйте commit/rollback на каждом пути, где открывается транзакция; не ждите пользовательского ввода или долгого ввода-вывода внутри транзакции.","Автоматически прерывайте простаивающие транзакции: Postgres idle_in_transaction_session_timeout, настройки соединений/тайм-аутов MySQL, maxLifetime пула приложения.","Настройте пул соединений по принципу «autocommit включён, короткие транзакции».","Настройте мониторинг: поднимайте оповещение при долгих сессиях idle-in-transaction."],"related":["active-sessions","blocking-sessions"]},"sol_kill":{"kind":"solution","title":"Решение: безопасно завершите проблемную сессию","detail":"Если блокирующая/долгая сессия заставляет ждать другие, её нужно контролируемо завершить. Завершение запускает откат, которому могут понадобиться пространство/время; будьте осторожны.","steps":["Сначала проверьте, что это за сессия (какой пользователь/приложение, какое задание), чтобы не прервать критичное задание.","Завершение: Oracle ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE; Postgres SELECT pg_terminate_backend(pid); MySQL KILL thread_id; MSSQL KILL session_id;","Если вы завершаете большую транзакцию записи, дождитесь окончания отката (отмена по undo/журналу).","После завершения убедитесь, что блокировки и ожидающие сессии исчезли; устраните первопричину (простой/пакет) окончательно."],"related":["blocking-sessions"]},"sol_batch":{"kind":"solution","title":"Первопричина: один огромный DML/отчёт","detail":"DML, обрабатывающий миллионы строк в одной транзакции, или очень долгий отчёт накапливают блокировки и undo/bloat на всё время выполнения.","steps":["Разбейте большой DML на части по дате/диапазону/ключу и выполняйте commit после каждой части.","Перенесите тяжёлый отчёт за пределы пиковых часов или на standby/реплику для чтения.","Оптимизируйте долгий запрос, чтобы сократить его общую длительность (а значит, и время удержания блокировок/undo).","Сделайте пакетное задание наблюдаемым: журналируйте прогресс и обеспечьте возможность продолжить с места остановки."],"related":["slow-queries"]},"sol_lock_backlog":{"kind":"solution","title":"Первопричина: очередь блокировок","detail":"Из-за удерживаемых блокировок долгая транзакция создала за собой очередь ожидания; множество сессий ждут один и тот же ресурс.","steps":["Определите сессию во главе цепочки блокировок (head blocker); только она может разгрузить очередь.","Сократите head blocker (commit/rollback) или контролируемо завершите его; наблюдайте, как очередь рассасывается.","Чтобы сократить время удержания блокировок, уменьшайте транзакции и задайте единый порядок доступа.","Где уместно, переходите на подходы, требующие более слабых блокировок (уровень строк, подходящий уровень изоляции)."],"related":["blocking-sessions","current-locks"]},"sol_bloat":{"kind":"solution","title":"Первопричина: накопление старых версий (undo/bloat)","detail":"Долгая транзакция мешает очистке старых версий строк, появившихся до неё. В Postgres это проявляется как раздувание таблиц/индексов, в Oracle — как давление на undo.","steps":["Найдите сессию, удерживающую самый старый xmin/транзакцию (Postgres backend_xmin, Oracle — самая старая активная транзакция).","Сократите/завершите эту транзакцию, чтобы очистка (VACUUM / повторное использование undo) снова могла продвигаться.","В Postgres после завершения транзакции выполните VACUUM для раздутых таблиц (при необходимости VACUUM FULL/pg_repack).","Закрепите паттерн приложения с короткими транзакциями и сделайте autovacuum агрессивнее."],"related":[]}}},"solver/plan-regression.json":{"id":"plan-regression","title":"План запроса деградировал (внезапно медленно)","icon":"📉","category":"Производительность","intro":"Запрос, вчера выполнявшийся быстро, сегодня внезапно медленный. Обычно оптимизатор переключился на худший план. Найдём первопричину среди устаревшей статистики, чувствительности к параметрам, роста объёма данных или внезапной смены плана и стабилизируем план.","start":"root","nodes":{"root":{"kind":"step","title":"Действительно ли план изменился и когда?","detail":"Есть ли несколько планов (plan_hash/plan_generation) для одного запроса и когда началось замедление? Совпадает ли это с недавним развёртыванием, обновлением, изменением параметров или большой загрузкой данных?","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":"Статистика может быть устаревшей","next":"stats"},{"label":"Один и тот же запрос ведёт себя по-разному в зависимости от параметра (sniffing/peeking)","next":"sniff"},{"label":"Объём данных значительно вырос","next":"sol_datagrowth"},{"label":"План внезапно изменился после обновления/сброса","next":"sol_planflip"}]},"stats":{"kind":"step","title":"Актуальна ли статистика?","detail":"Оптимизатор принимает решения на основе статистики. Если дата последнего анализа давняя или счётчик изменений высок, оценки могли реально отклониться, что приводит к плохому плану.","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":"Статистика устарела/отсутствует","next":"sol_stats"},{"label":"Статистика актуальна, но план по-прежнему чувствителен к параметрам","next":"sniff"}]},"sniff":{"kind":"step","title":"Есть ли чувствительность к параметрам?","detail":"Запрос выбирает и кэширует план по значению параметра при первом выполнении (MSSQL parameter sniffing / Oracle bind peeking). Для других значений при неравномерном распределении этот план может быть плохим.","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":"Да, план переключается между хорошим и плохим в зависимости от параметра","next":"sol_sniff"},{"label":"Правильный план известен, я хочу закрепить его навсегда","next":"sol_forceplan"}]},"sol_stats":{"kind":"solution","title":"Первопричина: устаревшая / отсутствующая статистика","detail":"Опираясь на старую статистику, не соответствующую реальности, оптимизатор неверно оценил число строк и выбрал плохой план. Это самая частая причина деградации плана.","steps":["Соберите свежую статистику по затронутым таблицам и индексам: Oracle DBMS_STATS.GATHER_TABLE_STATS, Postgres ANALYZE, MySQL ANALYZE TABLE, MSSQL UPDATE STATISTICS ... WITH FULLSCAN.","Создайте гистограммы/расширенную статистику для столбцов с неравномерным распределением.","После сбора статистики проверьте план с помощью EXPLAIN и убедитесь, что вернулся хороший план.","Чтобы проблема не повторялась, держите автоматический сбор статистики (autovacuum/auto update stats) включённым с подходящими порогами."],"related":["slow-queries"]},"sol_sniff":{"kind":"solution","title":"Первопричина: чувствительность к параметрам (sniffing / bind peeking)","detail":"Кэшированный план выбран под первое пришедшее значение параметра и плох для других значений при неравномерном распределении. Цель — обеспечить выбор плана, подходящего под значение.","steps":["В MSSQL задайте целевое поведение для критичного запроса с помощью OPTION (RECOMPILE) или OPTIMIZE FOR; для часто меняющихся значений предпочтителен recompile.","В Oracle используйте adaptive cursor sharing / поведение bind-aware; при необходимости разделите запрос по диапазонам значений.","В Postgres наблюдайте за выбором generic vs custom plan; при необходимости управляйте им через plan_cache_mode.","Если нужен единственный постоянный точный план, переходите к решению с закреплением плана."],"related":["slow-queries"]},"sol_forceplan":{"kind":"solution","title":"Решение: закрепите правильный план (baseline / force plan)","detail":"Если известен хороший план, его закрепление, чтобы оптимизатор от него не отклонялся, окончательно устраняет деградацию.","steps":["Oracle: создайте SQL Plan Baseline для хорошего плана (DBMS_SPM) или примите его через SQL Plan Management.","MSSQL: выберите хороший план в Query Store и принудительно закрепите его с помощью sp_query_store_force_plan.","MySQL/Postgres: управляйте планом с помощью подсказок оптимизатора (например, index hints) или pg_hint_plan.","Отслеживайте производительность после закрепления; если паттерн данных принципиально изменится, пересмотрите baseline."],"related":[]},"sol_datagrowth":{"kind":"solution","title":"Первопричина: объём данных вырос, план больше не подходит","detail":"По мере роста таблицы план, ранее бывший идеальным (например, nested loop), теперь плохо масштабируется; оптимизатору нужны статистика и пути доступа, правильно отражающие рост.","steps":["Соберите актуальную статистику, чтобы оптимизатор видел новое число строк.","Добавьте подходящие индексы для растущих таблиц; исправьте путь доступа, который теперь плохо масштабируется (например, полное сканирование/nested loop).","На очень больших таблицах ограничьте сканируемые данные секционированием по дате/диапазону.","Перепишите запрос с учётом роста (ранняя фильтрация, постраничный вывод, сводные/материализованные представления)."],"related":["slow-queries"]},"sol_planflip":{"kind":"solution","title":"Первопричина: внезапная смена плана (обновление / сброс кэша)","detail":"План внезапно изменился после обновления версии, изменения параметра или очистки кэша планов. Поведение оптимизатора или условия среды сместились.","steps":["Определите триггер изменения: обновление/патч, параметры оптимизатора, загрузка данных или перезапуск/сброс кэша.","Если причина в параметре, откатите изменение и переоцените его контролируемо.","Закрепите известный хороший план (baseline / force plan), чтобы оптимизатор снова не отклонился.","При деградации после обновления пересмотрите настройки совместимости/готовности оптимизатора."],"related":[]}}},"solver/replica-resync.json":{"id":"replica-resync","title":"Реплика сломана / нужна повторная синхронизация","icon":"🔗","category":"Репликация","intro":"Синхронизация между репликой/standby и первичным сервером нарушена. Определите, остановлена ли репликация, насколько велико отставание и нарушена ли цепочка позиций/GTID; затем выберите правильный путь: пропуск ошибки, ожидание или пересоздание (reseed). Включает предупреждение о split-brain.","start":"root","nodes":{"root":{"kind":"step","title":"Каково общее состояние репликации?","detail":"Сначала оцените состояние репликации: работают ли потоки применения, насколько велика задержка, есть ли ошибка? Также убедитесь, что оба узла не принимают запись одновременно (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":"Репликация остановлена / завершается с ошибкой","next":"error"},{"label":"Работает, но сильно отстаёт (большая задержка)","next":"lag"},{"label":"GTID/позиция нарушены, несогласованность — пересоздать","next":"sol_reseed"},{"label":"Оба узла пишут как первичные (подозрение на split-brain)","next":"sol_splitbrain"}]},"error":{"kind":"step","title":"Можно ли исправить ошибку или нужно пересоздание?","detail":"Изучите ошибку применения. Некоторые ошибки (дубликат ключа, отсутствующая строка) можно устранить безопасным пропуском одного события; но при несогласованности/повреждении данных единственный правильный путь — пересоздать реплику.","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":"Одна/несколько ошибок применения (можно пропустить)","next":"sol_skiperror"},{"label":"Неустранимая несогласованность / повреждение","next":"sol_reseed"}]},"lag":{"kind":"step","title":"Задержка временная или постоянная?","detail":"Посмотрите на динамику задержки во времени. Она сокращается (реплика догоняет) после временного всплеска нагрузки или продолжает расти, так что реплика никогда не догонит первичный сервер?","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":"Временный всплеск нагрузки; задержка сокращается","next":"sol_lag_wait"},{"label":"Продолжает расти; догнать не удаётся","next":"sol_lag_perm"}]},"sol_skiperror":{"kind":"solution","title":"Решение: осторожно пропустите ошибку применения","detail":"Если репликация остановилась из-за одной ошибки применения (например, дубликат ключа) и вы уверены, что данные действительно согласованы, можно пропустить это событие и продолжить поток. Это крайняя мера; слепой пропуск создаёт скрытое расхождение данных.","steps":["Сначала поймите, почему возникла ошибка, и проверьте согласованность данных, сравнив затронутую строку на обеих сторонах.","В MySQL без GTID пропустите одно событие: STOP REPLICA; SET GLOBAL sql_replica_skip_counter=1; START REPLICA; (до 8.0.26: sql_slave_skip_counter; осторожно).","С GTID, чтобы пропустить сбойную транзакцию, отметьте этот GTID пустой транзакцией, затем запустите реплику.","После пропуска выполните построчную проверку согласованности (checksum) между сторонами; при расхождении переходите к reseed."],"related":["replication-status"]},"sol_reseed":{"kind":"solution","title":"Решение: пересоздайте реплику (reseed / rebuild)","detail":"Если цепочка позиций/GTID нарушена, данные разошлись или ошибка неустранима, единственный надёжный путь — пересоздать реплику из свежей копии, снятой с первичного сервера.","steps":["Сделайте согласованный базовый бэкап с первичного сервера: MySQL xtrabackup/mysqldump или CLONE, Postgres pg_basebackup, Oracle RMAN duplicate, MSSQL full+log backup.","Очистите старое состояние на реплике и восстановите бэкап (Postgres pg_basebackup -R, MSSQL RESTORE ... WITH NORECOVERY).","Запустите репликацию с правильной позиции/GTID: MySQL CHANGE REPLICATION SOURCE TO ...; START REPLICA; для MSSQL снова добавьте базу данных в AG (automatic seeding).","Следите за догоном; когда задержка достигнет нуля, проверьте согласованность обеих сторон с помощью checksum."],"related":["replication-status"]},"sol_lag_wait":{"kind":"solution","title":"Первопричина: временный всплеск нагрузки (задержка сокращается)","detail":"Репликация исправна; задержка возникла из-за временного всплеска записи на первичном сервере или колебаний сети, но реплика догоняет.","steps":["Подтвердите несколькими измерениями, что задержка действительно уменьшается.","До окончания окна нагрузки при необходимости направляйте на первичный сервер только чтения, чувствительные к задержке.","Если повторяется, распределите всплески записи (пакетные задания, ETL) во времени или перенесите их за пределы пиковых часов.","Добавьте в мониторинг задержку сети/применения, чтобы временную задержку замечали до того, как она станет постоянной."],"related":["replication-status"]},"sol_lag_perm":{"kind":"solution","title":"Первопричина: реплика не успевает за первичным сервером","detail":"Сторона применения (apply/redo) не успевает за скоростью изменений на первичном сервере; задержка продолжает расти. Обычно причина — однопоточное применение, медленное оборудование реплики или узкое место в сети.","steps":["Найдите узкое место применения: ограничивает ли его однопоточное применение, диск/CPU реплики или пропускная способность сети?","Включите/увеличьте параллельное применение: несколько рабочих потоков реплики в MySQL (replica_parallel_workers); проверьте ресурсы применения в Postgres/Oracle.","Доведите оборудование реплики как минимум до уровня первичного сервера (особенно IOPS диска).","Сократите ненужные тяжёлые запросы на чтение на реплике, чтобы оставить ресурсы для применения; если она всё равно не успевает, переоцените ситуацию после reseed."],"related":["replication-status"]},"sol_splitbrain":{"kind":"solution","title":"ВНИМАНИЕ: split-brain (два первичных) — риск потери данных","detail":"Если оба узла принимают запись в роли первичного, на обеих сторонах накапливаются конфликтующие изменения; это самая опасная ситуация, которая может привести к потере данных. В первую очередь сведите запись к одному узлу.","steps":["Немедленно сосредоточьте запись на одном узле: строго направьте трафик приложения на один первичный сервер, второй переведите в режим только чтения/изолируйте.","Определите авторитетный (правильный) узел: решите, чьи данные считаются действительными с точки зрения бизнеса.","Сравните конфликтующие (расходящиеся) изменения на обеих сторонах; вручную согласуйте критичные изменения с проигравшей стороны.","Пересоздайте другой узел через reseed, используя авторитетный узел как источник; затем настройте механизм fencing/кворума, предотвращающий появление двух первичных."],"related":["replication-status"]}}},"solver/cache-hit-low.json":{"id":"cache-hit-low","title":"Низкий коэффициент попаданий в кэш (buffer/cache hit)","icon":"🎯","category":"Производительность","intro":"Коэффициент попаданий в кэш низкий; данные постоянно читаются с диска (или из источника). Определите, в чём проблема — в буферном/общем кэше БД или в кэше Redis/приложения, затем измерьте и устраните её.","start":"root","nodes":{"root":{"kind":"step","title":"У какого кэша низкий коэффициент попаданий?","detail":"Измерьте коэффициент попаданий и локализуйте проблему: низкий ли он у буферного/общего кэша базы данных (много физических чтений), или в кэше Redis/приложения много промахов и вытеснений?","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":"Низкий коэффициент попаданий в буферный/общий кэш БД","next":"dbcache"},{"label":"Много промахов / вытеснений в кэше Redis/приложения","next":"rediscache"}]},"dbcache":{"kind":"step","title":"Почему чтение идёт с диска: размер или паттерн сканирования?","detail":"Есть две типичные причины низкого коэффициента попаданий в буфер: часто используемые данные (working set) не помещаются в память (кэш слишком мал) или большие полные сканирования таблиц засоряют кэш одноразовыми блоками и вытесняют полезные страницы.","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 не помещается в RAM (кэш слишком мал)","next":"sol_sizing"},{"label":"Полные сканирования таблиц засоряют кэш","next":"sol_scan"}]},"rediscache":{"kind":"step","title":"Почему промахи в Redis: вытеснение по maxmemory или управление TTL/ключами?","detail":"Если коэффициент попаданий Redis низкий, либо память достигла maxmemory и ключи вытесняются, либо управление TTL/паттерн кэширования ключей слабые (холодные ключи, низкое повторное использование).","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 заполнен, сильное вытеснение","next":"sol_maxmem"},{"label":"Слабый паттерн TTL/ключей (низкое повторное использование)","next":"sol_ttl"}]},"sol_sizing":{"kind":"solution","title":"Первопричина: кэш слишком мал для working set","detail":"Часто используемые данные больше выделенного буфера/кэша, поэтому они постоянно вытесняются и перечитываются с диска. Коэффициент попаданий низкий, физических чтений много.","steps":["Увеличьте кэш, чтобы он покрывал working set: Oracle db_cache_size/SGA, Postgres shared_buffers, MySQL innodb_buffer_pool_size, MSSQL max server memory.","Если физической RAM недостаточно, добавьте память на сервер; это основной ресурс, с которым масштабируется БД.","Сократите working set: архивируйте/секционируйте старые данные, уберите ненужные столбцы/индексы.","После изменения сравните коэффициент попаданий и физические чтения, чтобы подтвердить улучшение."],"related":["cache-hit-ratio"]},"sol_scan":{"kind":"solution","title":"Первопричина: паттерн сканирования засоряет кэш","detail":"Большие полные сканирования таблиц заполняют кэш блоками, которые используются один раз и отбрасываются, вытесняя горячие страницы; это снижает коэффициент попаданий.","steps":["Получите планы выполнения запросов с наибольшим числом физических чтений и замените полные сканирования доступом по индексу (добавьте подходящие индексы).","Выбирайте только нужные столбцы вместо 'SELECT *'; сократите объём сканируемых блоков.","На больших таблицах сузьте сканирование до нужного диапазона с помощью секционирования.","Отделите тяжёлые аналитические задания/сканирования от кэша OLTP (реплика для чтения или отдельное временное окно)."],"related":["slow-queries","cache-hit-ratio"]},"sol_maxmem":{"kind":"solution","title":"Первопричина: вытеснение по maxmemory в Redis","detail":"Память Redis достигла лимита maxmemory; старые ключи вытесняются, освобождая место для новых (evicted_keys продолжает расти). При этом теряются ещё нужные записи и снижается коэффициент попаданий.","steps":["Повысьте maxmemory до реального working set или сократите набор данных.","Выберите политику вытеснения под паттерн доступа (например, LRU/LFU: allkeys-lfu часто эффективно отделяет горячие ключи от холодных).","Отсейте большие/редко используемые структуры; уменьшите значения, удалите ненужные поля, используйте подходящие типы данных.","Если потребность постоянная, масштабируйте Redis (больше RAM или кластер/шардинг) и начните отслеживать evicted_keys."],"related":[]},"sol_ttl":{"kind":"solution","title":"Первопричина: слабый паттерн TTL / кэширования","detail":"Либо ключи вообще не получают TTL, и память заполняется холодными данными, либо паттерн кэширования имеет низкое повторное использование, и большинство запросов заканчиваются промахами.","steps":["Назначайте кэшируемым ключам подходящие TTL, чтобы остывающие данные истекали сами.","Кэшируйте часто читаемые (горячие) данные; не кэшируйте редко используемые (не заполняйте кэш записями, в которые никогда не попадают).","В паттерне cache-aside корректно реализуйте заполнение после промаха и используйте блокировку/дедупликацию запросов против 'thundering herd'.","Отслеживайте соотношение keyspace_hits/misses и уточняйте TTL и структуру ключей на основе данных."],"related":[]}}},"solver/arch-oltp.json":{"id":"arch-oltp","title":"Эталонная архитектура: трёхуровневое OLTP-приложение","icon":"🏛️","category":"Эталонная архитектура","intro":"Классические бизнес-приложения (заказы, платежи, склад) выполняют большое число коротких однострочных транзакций, критичных к согласованности (OLTP). Это дерево пошагово показывает, начиная с определения профиля нагрузки, когда вводить основную реляционную базу данных, пул соединений, реплики для чтения, уровень кэша, бэкап + PITR и мониторинг. Цель — не «добавить всё заранее», а добавлять нужный компонент по мере подтверждения каждого узкого места.","start":"root","nodes":{"root":{"kind":"step","title":"Каков профиль нагрузки: состав транзакций и требования к согласованности?","detail":"Первое решение в архитектуре OLTP — является ли нагрузка действительно транзакционной (короткая смесь записи и чтения, требующая ACID). Данные по своей природе реляционные и согласованность критична (деньги, остатки, заказы), нагрузка в основном на чтение, или нагрузка пока очень низкая?","choices":[{"label":"Короткие транзакции, строгая согласованность (деньги/остатки/заказы)","next":"primary_db"},{"label":"Сильно преобладает чтение, мало записи, трафик растёт","next":"scale_reads"},{"label":"Нагрузка очень низкая; достаточно ли самой простой конфигурации?","next":"sol_single_node"}]},"primary_db":{"kind":"step","title":"Вы выбрали основную СУБД и беспокоитесь о масштабировании?","detail":"Реляционная СУБД — правильная основа для транзакционного ядра (PostgreSQL, MySQL/InnoDB, Oracle, SQL Server). Когда выбор СУБД ясен, следующий вопрос — масштабирование: пока достаточно одного экземпляра или растут число соединений и нагрузка чтения?","choices":[{"label":"СУБД выбрана; пока достаточно одного экземпляра","next":"sol_single_node"},{"label":"Число соединений резко выросло, ресурсы заканчиваются","next":"sol_pooler"},{"label":"Нагрузка чтения растёт и перегружает основной экземпляр записи","next":"scale_reads"}]},"scale_reads":{"kind":"step","title":"Как масштабировать чтение: реплика или кэш?","detail":"Если нагрузка чтения перегружает основной экземпляр, есть два взаимодополняющих варианта. Чтения, допускающие немного отстающие данные, такие как отчёты/списки, можно направить на реплику для чтения; если одни и те же «горячие» данные запрашиваются снова и снова, кэш с микросекундной задержкой (Redis) разгружает БД.","choices":[{"label":"Много чтений отчётов/списков; небольшая задержка допустима","next":"sol_read_replica"},{"label":"Одни и те же популярные данные читаются снова и снова","next":"sol_cache"}]},"sol_single_node":{"kind":"solution","title":"Рекомендация: начните с одного первичного сервера + пула + бэкапа","detail":"На раннем этапе держите архитектуру простой: достаточно одного реляционного первичного экземпляра, пула соединений перед ним и регулярных бэкапов. Добавляйте сложность, только когда этого требует измеренное узкое место.","steps":["Основная БД: ACID-совместимая реляционная СУБД, такая как PostgreSQL или MySQL/InnoDB; правильно спроектируйте схему, ограничения и индексы.","Пул соединений: с самого начала используйте пул на стороне приложения (например, HikariCP) или PgBouncer на стороне сервера; это предотвращает шторм соединений.","Бэкап + PITR: ежедневные полные бэкапы + архивирование WAL/binlog для восстановления на момент времени; храните бэкапы в отдельном хранилище.","Осторожно: не добавляйте реплики/шардинг/кэш на раннем этапе; попытка решить неизмеренную проблему лишь увеличивает поверхность отказов.","Мониторинг: с самого начала собирайте метрики задержки, доли ошибок и числа соединений; не пропустите сигнал роста."],"related":["decide-pooling","decide-backup"]},"sol_pooler":{"kind":"solution","title":"Рекомендация: добавьте пул соединений","detail":"В OLTP, когда каждый экземпляр приложения открывает собственные соединения, основная БД задыхается от накладных расходов на память и планировщик при тысячах соединений. Пул между ними распределяет небольшое число реальных соединений с БД между множеством запросов приложения.","steps":["PostgreSQL: PgBouncer (режим transaction pooling) сводит множество клиентов к небольшому числу серверных соединений.","MySQL: ProxySQL аналогично обеспечивает мультиплексирование соединений и маршрутизацию.","Размер пула: обычно пропорционален числу ядер CPU; достаточно нескольких десятков серверных соединений. «Больше — лучше» здесь не работает.","Осторожно: при transaction pooling функции уровня сессии (подготовленные операторы, временные таблицы, переменные сессии) ограничены; пишите приложение с учётом этого.","Далее: если пул тоже насыщается, проблема не в соединениях, а в длительности запросов/транзакций; ищите медленные запросы и долгие транзакции."],"related":["decide-pooling","connection-storm"]},"sol_read_replica":{"kind":"solution","title":"Рекомендация: добавьте реплику для чтения","detail":"Запись остаётся на первичном сервере, а трафик только для чтения (отчёты, списки, поиск) направляется на одну или несколько асинхронных реплик, получающих данные от первичного сервера. Это освобождает первичный сервер для записи и чтений, критичных к согласованности.","steps":["Топология: первичный -> одна или несколько асинхронных реплик; приложение разделяет чтение/запись и отправляет чтения на реплику.","СУБД: PostgreSQL streaming replication, реплики MySQL или управляемые реплики для чтения.","Когда: для чтений, допускающих небольшую задержку, таких как отчёты/аналитика/списки; когда нужно разгрузить CPU/IO первичного сервера.","Осторожно: задержка репликации приводит к устаревшему чтению; сценарии, которым нужно «сразу прочитать свою запись», обслуживайте с первичного сервера (read-your-writes).","Далее: если реплик тоже недостаточно, рассмотрите кэш для горячих данных или шардинг на стороне записи."],"related":["replication-lag","decide-shard-replica"]},"sol_cache":{"kind":"solution","title":"Рекомендация: добавьте уровень кэша (Redis)","detail":"Если одни и те же популярные данные (сессии, каталог, профили, фрагменты страниц) читаются многократно, поставьте кэш в памяти перед БД и обслуживайте большинство чтений, вообще не доходя до БД. Задержка снижается, а основная БД разгружается.","steps":["СУБД: паттерн cache-aside с Redis (или Memcached); приложение сначала проверяет кэш, а при промахе получает данные из БД и записывает их в кэш.","TTL + инвалидация: задайте каждому ключу разумный TTL; при записи удаляйте/обновляйте связанный ключ, чтобы не оставалось устаревших данных.","Когда: много чтений, данные меняются относительно редко, одни и те же ключи запрашиваются многократно.","Осторожно: cache stampede; когда истекает популярный ключ, тысячи запросов могут одновременно обрушиться на БД. Используйте блокировку/single-flight и TTL со случайным разбросом (jitter).","Осторожно: не считайте кэш «источником истины»; надёжная истина всегда хранится в БД, и приложение должно продолжать работать, обращаясь к БД, даже при отказе кэша."],"related":["decide-cache","cache-hit-low"]}}},"solver/arch-dwh.json":{"id":"arch-dwh","title":"Эталонная архитектура: хранилище данных / аналитика","icon":"🏛️","category":"Эталонная архитектура","intro":"Аналитические запросы (отчётность, агрегация, широкие сканирования) принципиально отличаются от OLTP-транзакций: их мало, но они очень широкие и долгие. Начиная с решения отделить аналитику от OLTP, это дерево пошагово проходит по таким компонентам, как метод загрузки (ETL vs ELT), колоночное хранение/columnstore, моделирование по схеме «звезда», секционирование + сжатие и предварительный расчёт с помощью материализованных представлений, взвешивая компромисс между параллелизмом и пропускной способностью.","start":"root","nodes":{"root":{"kind":"step","title":"Работает ли ваша аналитическая нагрузка в той же базе данных, что и OLTP?","detail":"Самая частая ошибка — запускать тяжёлые отчёты на рабочей транзакционной БД и ухудшать задержку OLTP. Первое решение — разделение: аналитика всё ещё работает на экземпляре OLTP, вы строите отдельную аналитическую среду или решаете, как перенести туда данные?","choices":[{"label":"Отчёты всё ещё работают на рабочей OLTP; они замедляют OLTP","next":"sol_separate"},{"label":"Отдельное хранилище есть; как загружать данные?","next":"load_method"},{"label":"Хранилище есть; запросы всё ещё медленные, как моделировать?","next":"model"}]},"load_method":{"kind":"step","title":"Где выполнять преобразование: до или после загрузки?","detail":"При переносе данных из источников в хранилище будете ли вы выполнять очистку/преобразование в отдельном уровне обработки до загрузки (ETL) или сначала загрузите сырые данные в хранилище и преобразуете их с помощью SQL внутри мощного движка хранилища (ELT)? Решение зависит от вычислительной мощности хранилища и потребности в гибкости схемы.","choices":[{"label":"Преобразование сложное/внешнее; выполнять до загрузки","next":"sol_etl"},{"label":"Хранилище мощное; загружать сырые данные, преобразовывать внутри с помощью SQL","next":"sol_elt"}]},"model":{"kind":"step","title":"Узкое место запросов — в структуре хранения или в модели данных?","detail":"Если аналитика медленная, есть два основных рычага. Широкие сканирования при построчном хранении читают всю строку; колоночное хранение читает только нужные столбцы и хорошо сжимается. Со стороны модели избыточная нормализация означает множество соединений, замедляющих запросы; схема «звезда» (факты + измерения) упрощает аналитику.","choices":[{"label":"Широкие агрегирующие сканирования медленные; читается мало столбцов","next":"sol_columnar"},{"label":"Слишком много соединений, сложная нормализованная схема","next":"sol_star"},{"label":"Один и тот же тяжёлый отчёт многократно даёт одинаковый результат","next":"sol_matview"}]},"sol_separate":{"kind":"solution","title":"Рекомендация: отделите аналитику от OLTP","detail":"Транзакционная и аналитическая нагрузки душат друг друга на одной СУБД: долгие отчёты удерживают блокировки/IO и задерживают короткие транзакции. Вынос аналитики в отдельную цель (реплика для чтения или отдельное аналитическое хранилище) разгружает обе стороны.","steps":["Первый шаг: направляйте лёгкие отчёты на реплику OLTP для чтения, а тяжёлую аналитику — в отдельный аналитический движок.","Аналитический движок: колоночный/аналитический движок (например, SQL Server с columnstore, ClickHouse или колоночное хранилище) во много раз быстрее построчного хранения на широких сканированиях.","Параллелизм vs пропускная способность: OLTP настраивается на множество мелких транзакций (высокий параллелизм), хранилище — на немногие огромные запросы (высокая пропускная способность); оптимизировать и то и другое на одной машине нельзя.","Осторожно: хранилище — не «копия продуктива в реальном времени»; это данные, загружаемые с интервалами, с задержкой, но организованные для анализа.","Далее: после разделения выберите метод загрузки (ETL/ELT) и модель (схема «звезда»)."],"related":["decide-sqlnosql","slow-query"]},"sol_etl":{"kind":"solution","title":"Рекомендация: ETL (преобразование до загрузки)","detail":"В ETL данные извлекаются из источников (Extract), очищаются и преобразуются в отдельном уровне обработки (Transform) и только затем загружаются в хранилище (Load). В хранилище попадают только чистые данные, соответствующие модели.","steps":["Когда: преобразование сложное, нужно объединять несколько источников, или сырые/чувствительные данные не должны попадать в хранилище.","Поток: источник (OLTP/файл/API) → уровень преобразования → пакетная загрузка в хранилище.","Плюс: хранилище остаётся чистым; чувствительные данные можно маскировать до загрузки.","Минус: преобразование зависит от мощности отдельной системы; гибкость SQL хранилища не используется.","Осторожно: загружайте в пакетном окне (например, ночью); планируйте так, чтобы избежать конфликтов с тяжёлыми запросами во время загрузки."],"related":["decide-capacity"]},"sol_elt":{"kind":"solution","title":"Рекомендация: ELT (преобразование внутри хранилища)","detail":"В ELT сырые данные сначала загружаются в хранилище, а преобразование выполняется внутри собственного мощного SQL-движка хранилища. Поскольку у современных колоночных хранилищ высокая вычислительная мощность, вы перемещаете данные один раз и обрабатываете их внутри, а не несёте преобразование к данным.","steps":["Когда: движок хранилища мощный и масштабируемый; вы также хотите сохранять сырые данные (аудит/повторная обработка).","Поток: источник → сырой слой (staging) → SQL/представления преобразования → смоделированный слой фактов/измерений.","Плюс: один движок, гибкость; так как сырые данные сохраняются, преобразование можно изменить позже и пересчитать.","Минус: сырые (возможно, чувствительные) данные попадают в хранилище; контроль доступа и маскирование обязательны.","Осторожно: моделируйте слоями (raw → staging → mart); пишите преобразования идемпотентно, чтобы повторные запуски были безопасны."],"related":["decide-capacity"]},"sol_columnar":{"kind":"solution","title":"Рекомендация: колоночное хранение / columnstore + сжатие","detail":"Аналитические запросы обычно сканируют несколько столбцов по всем строкам. Колоночное хранение держит данные по столбцам; оно читает только нужные столбцы, очень хорошо сжимает, так как похожие значения расположены рядом, и IO резко снижается.","steps":["СУБД/функция: columnstore index в SQL Server, ClickHouse или колоночные хранилища; колоночные расширения на стороне PostgreSQL.","Сжатие: колоночная структура даёт высокую степень сжатия; меньше диска, меньше читаемых байт, быстрее сканирование.","Секционирование: секционируйте большие таблицы фактов по дате/диапазону, чтобы partition pruning полностью пропускал ненужные секции.","Осторожно: колоночное хранение плохо подходит для множества однострочных обновлений/точечных чтений; не переносите на него OLTP.","Параллелизм vs пропускная способность: колоночное хранение блистает на немногих огромных сканирующих запросах, а не на тысячах мелких параллельных запросов."],"related":["decide-partition","io-bottleneck"]},"sol_star":{"kind":"solution","title":"Рекомендация: схема «звезда» (факты + измерения)","detail":"В аналитической модели в центре находится таблица фактов с мерами, окружённая описательными таблицами измерений. По сравнению с чрезмерно нормализованной схемой число соединений снижается, запросы упрощаются, а СУБД строит лучшие планы.","steps":["Структура: в центре факты (меры + ключи измерений); измерения (время, товар, клиент) образуют вокруг звезду.","Когда: отчёты соединяют много таблиц, а нормализованная схема тонет в соединениях.","Денормализация: осознанная денормализация нормальна в аналитике; согласованность обеспечивается в процессе загрузки (ETL/ELT).","Далее: заранее рассчитывайте часто выполняемые тяжёлые агрегации с помощью материализованных представлений.","Осторожно: не переводите OLTP на схему «звезда»; эта модель предназначена для аналитики, а не для транзакционной записи."],"related":["decide-index"]},"sol_matview":{"kind":"solution","title":"Рекомендация: предварительный расчёт с помощью материализованных представлений","detail":"Если одна и та же тяжёлая агрегация (дневная выручка, месячная сводка) выполняется многократно и даёт одинаковый результат, физически сохраните результат в материализованном представлении, а отчёты пусть читают заранее рассчитанный результат. Тяжёлое вычисление выполняется один раз, а запросы возвращаются мгновенно.","steps":["Когда: определённые сводные запросы часто и многократно сканируют одну и ту же большую таблицу.","Обновление: обновляйте материализованное представление по расписанию (например, ночной пакет или инкрементально); частоту задайте по требованиям к актуальности.","СУБД: PostgreSQL materialized view (REFRESH), Oracle materialized view (вплоть до query rewrite), SQL Server indexed view.","Осторожно: у представления есть окно актуальности; там, где нужны данные в реальном времени, обязателен живой запрос.","Далее: если обновление дорого, рассмотрите инкрементальное обновление или обновление по секциям."],"related":["decide-partition"]}}},"solver/arch-microservice.json":{"id":"arch-microservice","title":"Эталонная архитектура: уровень данных микросервисов","icon":"🏛️","category":"Эталонная архитектура","intro":"В микросервисах самое важное решение — владение данными: каждый сервис должен владеть своими данными (database-per-service) и не обращаться напрямую к таблицам другого сервиса. Это дерево пошагово рассматривает антипаттерн общей БД, достижение согласованности между сервисами без распределённых транзакций (saga + outbox), синхронизацию данных через CDC и межсервисные запросы (API composition vs модель чтения CQRS). Цель — сохранить сервисы действительно независимыми и раздельно развёртываемыми.","start":"root","nodes":{"root":{"kind":"step","title":"Как сервисы владеют данными: общая БД или по одной на сервис?","detail":"Изоляция данных — основа микросервисной архитектуры. Сервисы используют одну общую базу данных (скрытая зависимость), у каждого сервиса своя база данных (правильный паттерн), или реальная проблема — согласованность/запросы между сервисами? Сначала определите текущее состояние.","choices":[{"label":"Все сервисы используют одну общую БД","next":"sol_db_per_service"},{"label":"У каждого сервиса своя БД, но операциям нужна согласованность между сервисами","next":"consistency"},{"label":"У каждого сервиса своя БД, но нужны объединённые запросы/отчёты","next":"query"}]},"consistency":{"kind":"step","title":"Как обеспечить согласованность межсервисного бизнес-процесса?","detail":"Для операции, охватывающей несколько сервисов (создать заказ → принять оплату → списать остаток), классическая распределённая транзакция (2PC) хрупка и не масштабируется. Вместо этого разбейте процесс на шаги «локальная транзакция + событие» (saga) и надёжно публикуйте события (outbox). С какой проблемой вы имеете дело?","choices":[{"label":"Я пытаюсь сделать многосервисный процесс атомарным (2PC?)","next":"sol_saga"},{"label":"Не удаётся согласовать локальную запись и публикацию события (dual write)","next":"sol_outbox"}]},"query":{"kind":"step","title":"Как вы будете обслуживать запрос, показывающий данные из нескольких сервисов?","detail":"Когда данные разделены между сервисами, общий экран невозможно построить одним join. Есть два основных подхода: вызывать API соответствующих сервисов во время запроса и объединять результаты (API composition) или поддерживать заранее объединённую модель чтения, наполняемую событиями (CQRS). Выбор определяют частота и сложность запросов.","choices":[{"label":"Простое, редкое объединение; достаточно вызвать несколько сервисов и объединить","next":"sol_api_composition"},{"label":"Сложный/частый объединённый запрос; нужна готовая модель чтения","next":"sol_cqrs"}]},"sol_db_per_service":{"kind":"solution","title":"Рекомендация: database-per-service (разделите общую БД)","detail":"Совместное использование одной базы данных сервисами — скрытая и опасная зависимость: изменение схемы одного сервиса ломает другие, владение таблицами становится неясным, а независимое развёртывание — невозможным. Каждый сервис должен владеть своими данными.","steps":["Принцип: каждый сервис читает/пишет только свою базу данных; он не может обращаться напрямую к таблицам другого сервиса, а лишь запрашивать данные через API этого сервиса.","Независимость СУБД: каждый сервис может выбрать СУБД под свои потребности (например, PostgreSQL для заказов, MongoDB для каталога, Elasticsearch для поиска, Redis для корзины).","Антипаттерн: «общая база данных» — общие таблицы, перекрёстные соединения и единая схема; это не микросервисы, а распределённый монолит.","Декомпозиция: сначала логическое разделение (отдельная схема/пользователь), затем физическое; уберите соединения между таблицами разных сервисов.","Осторожно: после разделения возникают проблемы согласованности (saga/outbox) и объединённых запросов (composition/CQRS); это естественная цена такого перехода."],"related":["decide-isolation","decide-sqlnosql"]},"sol_saga":{"kind":"solution","title":"Рекомендация: паттерн saga (избегайте распределённых транзакций)","detail":"Не пытайтесь сделать операцию, охватывающую несколько сервисов, атомарной одной распределённой транзакцией (2PC); 2PC удерживает блокировки и приносит хрупкость единой точки и низкую доступность. Вместо этого разбейте процесс на шаги, каждый из которых — локальная транзакция в своём сервисе, и отменяйте их компенсирующими шагами при сбое.","steps":["Принцип: моделируйте процесс как цепочку локальных транзакций + событий; каждый шаг запускает следующий.","Компенсация: если шаг завершился ошибкой, выполните компенсирующие действия, отменяющие предыдущие шаги (например, отмените заказ, если оплата не прошла).","Хореография vs оркестрация: в простых процессах сервисы запускают друг друга событиями (хореография); в сложных процессах шагами управляет центральный оркестратор.","Осторожно: saga не атомарна; она обеспечивает согласованность в конечном счёте (eventual consistency). Промежуточные состояния могут быть видны извне, поэтому проектируйте бизнес-правила с учётом этого.","Осторожно: не используйте 2PC/XA в высоконагруженных микросервисах; блокирование и длительность удержания блокировок остановят систему."],"related":["decide-isolation","long-transaction"]},"sol_outbox":{"kind":"solution","title":"Рекомендация: Transactional outbox (ловушка двойной записи)","detail":"Когда сервису нужно и записать данные в свою БД, и опубликовать событие, выполнение этих действий по отдельности — ловушка «двойной записи» (dual write): если одно удалось, а другое нет, система остаётся в несогласованном состоянии. Решение — записать событие в таблицу outbox в ТОЙ ЖЕ локальной транзакции, что и бизнес-данные, а затем публиковать его отдельным процессом.","steps":["Принцип: Бизнес-запись и запись события пишутся в таблицу outbox в одной локальной транзакции; выполняются либо обе, либо ни одна (атомарно).","Публикация: Отдельный процесс relay/CDC читает строки outbox, публикует их в брокер сообщений (например, Kafka) и помечает их.","С CDC: Инструмент CDC, такой как Debezium, читает журнал БД (WAL/binlog) и надёжно переносит изменения outbox в Kafka.","Идемпотентность: Потребители могут получить одно и то же событие более одного раза; обрабатывайте идемпотентно по id события (исходите из доставки at-least-once).","Внимание: Не используйте напрямую подход «сначала записать в БД, затем опубликовать в Kafka»; сбой в момент публикации приводит к потере события."],"related":["replication-lag"]},"sol_api_composition":{"kind":"solution","title":"Рекомендация: API composition (объединение во время запроса)","detail":"Там, где нужны объединённые данные, вызывающая сторона (например, gateway/BFF) обращается к API соответствующих сервисов и объединяет результаты в памяти. Это самое простое решение для несложных соединений, охватывающих немного сервисов; дополнительная копия данных не хранится.","steps":["Принцип: Компоновщик (API gateway/BFF) вызывает service-A и service-B, объединяет результат и возвращает его.","Когда: Соединение простое, охватывает немного сервисов, частота запросов умеренная; вы не хотите нести затраты на сопровождение дополнительной модели чтения.","Плюс: Нет дополнительного хранилища данных и синхронизации; каждый сервис возвращает свои актуальные данные.","Внимание: Для соединений между многими сервисами или больших наборов данных задержка накапливается, а соединения в памяти дорогостоящи.","Далее: Если соединение становится сложным или очень частым, переходите на модель чтения CQRS."],"related":[]},"sol_cqrs":{"kind":"solution","title":"Рекомендация: модель чтения CQRS (наполняется событиями)","detail":"Для сложных и частых объединённых запросов держите заранее соединённую, оптимизированную под запросы модель чтения (материализованное представление/денормализованная копия), наполняемую событиями от сервисов. Сторона записи остаётся в сервисах; сторона чтения быстро отдаёт готовые данные из одного места.","steps":["Принцип: Сервисы публикуют изменения состояния как события; проекция слушает эти события и обновляет модель чтения.","Выбор хранилища: Выбирайте хранилище модели чтения по типу запросов: Elasticsearch для поиска, MongoDB для гибких документов, Redis для быстрого доступа по ключу, PostgreSQL для реляционных отчётов.","Когда: Объединённый запрос сложный/частый, требуется низкая задержка, а стоимость API composition выросла.","Внимание: Модель чтения согласована в конечном счёте (eventual consistency); она немного отстаёт, пока события не обработаны.","Идемпотентность: Проекция может увидеть одно и то же событие дважды; обновляйте идемпотентно по id события и при необходимости отслеживайте порядок/версию."],"related":["decide-cache"]}}},"solver/arch-eventdriven.json":{"id":"arch-eventdriven","title":"Эталонная архитектура: Событийно-ориентированная / CQRS","icon":"🏛️","category":"Эталонная архитектура","intro":"В событийно-ориентированной архитектуре сервисы не вызывают друг друга напрямую, а публикуют события в журнал событий (например, Kafka), а потребители обрабатывают их асинхронно. Начиная с построения событийной магистрали, это дерево шаг за шагом проходит через event sourcing (получение состояния из событий), разделение команд и запросов с CQRS, проекцию модели чтения, идемпотентность, допустимость eventual consistency и заблуждение «доставки exactly-once». Цель — слабо связанная, масштабируемая система, компромиссы согласованности которой хорошо понятны.","start":"root","nodes":{"root":{"kind":"step","title":"Какое решение стоит перед вами в событийно-ориентированной архитектуре?","detail":"Основа событийно-ориентированной системы — надёжный журнал событий, связывающий сервисы асинхронными событиями. Вы ещё строите магистраль, хотите получать состояние из событий (event sourcing) или разделяете пути чтения и записи (CQRS)?","choices":[{"label":"Я строю событийную магистраль, связывающую сервисы","next":"sol_event_log"},{"label":"Я хочу получать состояние из истории событий","next":"sol_event_sourcing"},{"label":"Я хочу разделить пути чтения и записи (CQRS)","next":"cqrs_split"}]},"cqrs_split":{"kind":"step","title":"Как вы построите сторону чтения в CQRS и какова ваша допустимость рассогласования?","detail":"В CQRS стороны команд (запись) и запросов (чтение) разделены; события записи обновляют проекцию, наполняющую модель чтения. Ключевой вопрос: допустимо ли отставание модели чтения от записей (eventual consistency) или вы ожидаете, что каждое событие будет обработано ровно один раз?","choices":[{"label":"Я буду наполнять модель чтения из событий через проекцию","next":"sol_read_model"},{"label":"Чтение должно быть актуальным; отставание недопустимо","next":"sol_consistency"},{"label":"Я хочу, чтобы события обрабатывались «ровно один раз»","next":"sol_idempotency"}]},"sol_event_log":{"kind":"solution","title":"Рекомендация: надёжный журнал событий как основа системы","detail":"В центре событийно-ориентированной архитектуры — надёжный упорядоченный журнал событий, в который пишут производители и который потребители читают в своём темпе. Эта магистраль развязывает сервисы по времени и масштабу.","steps":["Движок: Apache Kafka (или аналогичная платформа на основе журнала): секционированный, постоянный журнал событий с возможностью повторного воспроизведения.","Секции и порядок: Порядок гарантируется только в пределах секции; сохраняйте порядок, направляя события одной сущности на один и тот же ключ.","Независимость потребителей: Каждая группа потребителей хранит собственный offset; она может перечитывать/перематывать без влияния на производителя.","Хранение: Храните журнал достаточно долго, чтобы новые потребители и повторная обработка могли прочитать историю.","Внимание: Kafka — не очередь, а журнал с повторным воспроизведением; исходите из доставки at-least-once и пишите идемпотентных потребителей."],"related":[]},"sol_event_sourcing":{"kind":"solution","title":"Рекомендация: Event sourcing (получение состояния из событий)","detail":"В event sourcing хранится не текущее состояние сущности, а все применённые к ней (неизменяемые) события изменений; текущее состояние получается последовательным применением этих событий. Это даёт полную историю аудита и возможность вернуться к любому моменту в прошлом.","steps":["Принцип: Поток событий — источник истины; состояние восстанавливается воспроизведением событий с начала (или со снимка + последующих событий).","Снимки: Для длинных потоков делайте периодические снимки, чтобы не воспроизводить всё с начала каждый раз, и применяйте поверх них более поздние события.","Плюс: Полный журнал аудита, ответ на вопрос «как мы пришли к этому состоянию» во времени и возможность получать разные модели чтения.","Внимание: Схемы событий эволюционируют; чтение старых событий требует версионирования/upcasting, потому что события хранятся вечно.","Внимание: Нужен не в каждом сценарии; если достаточно текущего состояния, классический CRUD проще. Обычно используется вместе с CQRS."],"related":[]},"sol_read_model":{"kind":"solution","title":"Рекомендация: проекция модели чтения","detail":"В CQRS сторона записи порождает события; проекция слушает эти события и обновляет оптимизированную под запросы модель чтения (денормализованное представление). Чтение обслуживается из этой готовой модели, полностью отделённой от пути записи, и обе стороны масштабируются независимо.","steps":["Принцип: События → проекция → хранилище чтения; запросы на чтение обращаются только к этой денормализованной модели.","Выбор хранилища: По типу запросов: Elasticsearch для поиска, MongoDB для гибких документов, Redis для быстрого доступа, PostgreSQL для реляционных данных.","Возможность перестроения: Поскольку модель чтения получена из событий, её можно удалить и перестроить с нуля, перечитав журнал.","Идемпотентность: Проекция может увидеть одно и то же событие более одного раза; обновляйте идемпотентно по id/версии события.","Внимание: Модель чтения согласована в конечном счёте; она отстаёт от записей, пока событие не обработано, поэтому проектируйте UI с учётом этого."],"related":["decide-cache"]},"sol_consistency":{"kind":"solution","title":"Рекомендация: проектируйте с учётом eventual consistency","detail":"В событийно-ориентированных/CQRS-системах модель чтения немного отстаёт от записей (eventual consistency). Вместо попыток это «устранить» проектируйте так, чтобы это допускать; немногие потоки, требующие строгой немедленной согласованности, держите отдельно.","steps":["Принцип: Большинство бизнес-потоков допускают согласованность с небольшой задержкой; давайте пользователю обратную связь «запрос получен, обрабатывается».","Read-your-writes: Если пользователи должны сразу видеть свои записи, обслуживайте это чтение со стороны записи/синхронно.","Ядро со строгой согласованностью: Работу, требующую абсолютной немедленной согласованности, например денежный баланс, держите внутри одного сервиса/БД с ACID-транзакцией; не распределяйте её через события.","Внимание: Не скрывайте eventual consistency; явно обрабатывайте промежуточные/переходные состояния на экранах и в бизнес-правилах.","Внимание: Цель «сделать всю систему строго согласованной» уничтожает преимущества событийно-ориентированной архитектуры (масштаб, слабая связанность)."],"related":["decide-ha","replication-lag"]},"sol_idempotency":{"kind":"solution","title":"Рекомендация: идемпотентность (миф об exactly-once)","detail":"В распределённом обмене сообщениями сквозная «доставка exactly-once» на практике — заблуждение; дубликаты сообщений неизбежны из-за сети и повторных попыток. Правильная цель — идемпотентная обработка: двукратная обработка одного события должна давать тот же результат, что и однократная.","steps":["Принцип: Примите доставку at-least-once; сделайте дубликаты безвредными на стороне потребителя.","Метод: Присваивайте каждому событию уникальный id; записывайте обработанные id и пропускайте их при повторном поступлении (дедупликация) или используйте естественно идемпотентные операции (upsert, set).","Семантика exactly-once: Функция «exactly-once» в Kafka действует только в пределах собственной транзакционной границы Kafka; побочные эффекты во внешней БД без идемпотентности не гарантируются.","Порядок: Избегайте переупорядочивания, направляя события одной сущности на один и тот же ключ секции.","Внимание: Допущение «я настроил доставку exactly-once, дедупликация не нужна» порождает несогласованность в production; всегда обрабатывайте идемпотентно."],"related":[]}}},"solver/arch-multiregion.json":{"id":"arch-multiregion","title":"Эталонная архитектура: multi-region / глобальная","icon":"🏛️","category":"Эталонная архитектура","intro":"Когда пользователи географически распределены или требуется устойчивость к региональной катастрофе, данные распределяются по нескольким регионам. Это дерево шаг за шагом проходит через локальность чтения (реплика для чтения в каждом регионе), топологию записи (single-leader vs multi-leader vs кворум), резидентность данных/соответствие требованиям (GDPR), разрешение конфликтов и компромиссы между RPO/RTO и задержкой. Основная истина: из-за законов физики низкая задержка, строгая согласованность и запись в нескольких регионах не могут быть полностью достигнуты одновременно; осознанный компромисс обязателен.","start":"root","nodes":{"root":{"kind":"step","title":"Зачем вам multi-region: локальность чтения, запись/устойчивость или соответствие требованиям?","detail":"Multi-region архитектура дорога и сложна; уточните мотивацию. Вам нужна низкая задержка чтения для удалённых пользователей, межрегиональная запись/устойчивость к катастрофам, или реальное требование — чтобы данные оставались в определённой стране (юридическая резидентность)?","choices":[{"label":"Быстрое чтение для удалённых пользователей; запись может оставаться в одном регионе","next":"sol_read_locality"},{"label":"Нужна межрегиональная запись / устойчивость к потере региона","next":"write_topology"},{"label":"Данные по закону должны оставаться в определённой стране/регионе","next":"sol_data_residency"}]},"write_topology":{"kind":"step","title":"Какой должна быть топология записи: single-leader, multi-leader или кворум?","detail":"Для межрегиональной записи ключевое решение — модель лидерства. Все записи идут в один регион (просто, согласованно, но удалённая запись медленная), каждый регион может писать (низкая задержка записи, но конфликты), или запись принимается при подтверждении большинства регионов (кворум: согласованно, но с дополнительной задержкой)?","choices":[{"label":"Один регион пишет, остальные читают (просто и согласованно)","next":"sol_single_leader"},{"label":"Каждый регион может писать локально (низкая задержка записи)","next":"sol_multi_leader"},{"label":"Запись с подтверждением большинства; согласованность прежде всего","next":"sol_quorum"}]},"sol_read_locality":{"kind":"solution","title":"Рекомендация: реплика для чтения в каждом регионе (локальность чтения)","detail":"Запись остаётся в одном основном регионе, а в каждом регионе пользователей размещается реплика для чтения, получающая данные от него. Пользователи читают с ближайшей реплики, и задержка чтения снижается; поскольку запись по-прежнему идёт в один центр, модель согласованности остаётся простой.","steps":["Топология: Один основной регион (запись) → асинхронная реплика для чтения в каждом регионе пользователей.","Движки: Потоковая репликация PostgreSQL, межрегиональные реплики MySQL или управляемые глобальные реплики для чтения.","Маршрутизация: Приложение направляет чтение на ближайшую реплику, а запись — в основной регион (geo DNS / умный маршрутизатор).","Внимание: Отставание межрегиональной репликации приводит к чтению устаревших данных; потоки, которым нужен read-your-writes, должны читать с основного узла.","RPO/RTO: Если основной регион выходит из строя, асинхронную реплику можно повысить с некоторой потерей данных (RPO > 0); подготовьте план failover заранее."],"related":["replication-lag","decide-shard-replica"]},"sol_single_leader":{"kind":"solution","title":"Рекомендация: запись с одним лидером (single-leader)","detail":"Все записи идут в один регион-лидер; остальные регионы — реплики для чтения, асинхронно получающие данные от лидера. Это самая простая и наиболее строго согласованная модель; конфликтов нет, потому что точка записи одна. Цена — высокая задержка записи из удалённых регионов.","steps":["Принцип: Единый источник записи → нет конфликтов, простая модель согласованности.","Когда: Объём записи может обслуживаться в одном регионе, а задержка удалённой записи допустима.","Failover: Если регион-лидер выходит из строя, реплика повышается до лидера; автоматический/контролируемый failover и защита от split-brain обязательны.","RPO/RTO: При асинхронной репликации при failover может быть потерян небольшой объём данных (RPO); синхронная репликация даёт RPO=0, но увеличивает задержку записи.","Внимание: Запись от удалённых пользователей медленная; если локальность записи критична, рассмотрите multi-leader или кворум."],"related":["decide-ha","failover-drill"]},"sol_multi_leader":{"kind":"solution","title":"Рекомендация: запись с несколькими лидерами (multi-leader) + разрешение конфликтов","detail":"Поскольку каждый регион может писать локально, задержка записи низкая, и даже если один регион отрезан, остальные продолжают писать. Цена высока: два региона могут по-разному обновить одну и ту же запись, и вам нужно спроектировать разрешение конфликтов.","steps":["Принцип: Каждый регион — лидер; записи асинхронно распространяются в другие регионы и объединяются.","Разрешение конфликтов: Last-write-wins (просто, но с риском потери данных), объединение на уровне приложения или бесконфликтные типы данных, такие как CRDT.","Примеры движков: Конфигурации multi-master/multi-leader (например, EDB Postgres Distributed (BDR)/pgEdge для PostgreSQL, Redis Enterprise Active-Active (CRDT), CouchDB). Cassandra не multi-leader, а leaderless; с LOCAL_ONE/LOCAL_QUORUM она допускает локальную запись в каждом регионе с аналогичным результатом и разрешает конфликты по принципу last-write-wins (временные метки).","Когда: Задержка локальной записи и независимость регионов важнее строгой согласованности.","Внимание: Проектируйте разрешение конфликтов с самого начала; допущение «конфликтов не будет» приводит к незаметной потере данных. Следите за межрегиональными коллизиями при генерации уникальных ID."],"related":["decide-ha"]},"sol_quorum":{"kind":"solution","title":"Рекомендация: запись на основе кворума (подтверждение большинства)","detail":"Запись принимается, когда её подтверждает большинство (кворум) регионов/реплик; если чтение тоже идёт к большинству, гарантируется последнее значение. Модель устойчива к потере региона и строго согласована; цена — задержка, пока каждая запись достигает большинства.","steps":["Принцип: Подтверждение большинства по правилу W + R > N; даже если регион меньшинства выходит из строя, система продолжает согласованно писать.","Примеры движков: Распределённые БД на основе консенсуса (например, системы на Raft/Paxos), уровень согласованности QUORUM в Cassandra.","Когда: Требуются и устойчивость к региональной катастрофе, и строгая согласованность, а более высокая задержка записи допустима.","RPO/RTO: Поскольку большинство пишет надёжно, RPO≈0; при автоматическом восстановлении большинства после потери региона RTO может быть низким.","Внимание: Кворум между географически удалёнными регионами добавляет десятки ms к каждой записи из-за задержки, обусловленной скоростью света; тщательно выбирайте число и размещение регионов."],"related":["decide-ha","decide-shard-replica"]},"sol_data_residency":{"kind":"solution","title":"Рекомендация: резидентность данных / соответствие требованиям (GDPR)","detail":"Некоторые данные (персональные, медицинские, финансовые) по закону должны оставаться в пределах определённой страны/региона (например, GDPR). В этом случае архитектура проектируется не по принципу «копировать везде», а так, чтобы данные были географически закреплены.","steps":["Принцип: Храните персональные/регулируемые данные в регионе их происхождения; ограничьте или запретите репликацию за пределы региона.","Региональное секционирование: Направляйте пользователей в регион их регистрации (geo-partitioning); каждый регион хранит данные своих резидентов.","Глобальные и локальные данные: Глобальные данные, например каталог, можно реплицировать везде; персональные данные остаются в регионе.","Внимание: Бэкапы и реплики для чтения тоже перемещают данные; регион хранения бэкапов и регион DR также входят в область соответствия требованиям.","Внимание: Решение вроде «скопируем везде ради низкой задержки» может быть нарушением закона для регулируемых данных; закладывайте ограничения резидентности в архитектуру с самого начала."],"related":["security-incident","audit-setup"]}}},"solver/arch-highscale.json":{"id":"arch-highscale","title":"Эталонная архитектура: высокий масштаб / высокий трафик","icon":"🏛️","category":"Эталонная архитектура","intro":"По мере роста трафика у защиты базы данных есть правильный порядок: сначала простые и дешёвые рычаги, затем сложные. Это дерево шаг за шагом проходит через порядок вертикальное масштабирование → реплика для чтения → шардинг, паттерны кэширования (cache-aside, write-back), буферизацию с помощью очереди + backpressure, ограничение частоты запросов (rate limiting), изоляцию базы данных с помощью bulkhead/circuit breaker и предотвращение горячих точек. Основной принцип: трудно обратимые решения, такие как шардинг, оставляйте напоследок и сначала исчерпайте дешёвые выигрыши.","start":"root","nodes":{"root":{"kind":"step","title":"Где узкое место: перегружен один экземпляр, преобладает чтение или запись, или трафик идёт всплесками?","detail":"Чтобы решить проблему масштабирования в правильном месте, сначала определите узкое место. Один экземпляр упирается в CPU/память, преобладает чтение, запись достигает предела, или внезапные пики трафика давят на базу данных? Каждый случай указывает на свой компонент.","choices":[{"label":"Один экземпляр перегружен; простые рычаги я ещё не пробовал","next":"scale_path"},{"label":"Чтение очень интенсивное и растёт","next":"sol_cache"},{"label":"Внезапные пики трафика / всплески нагрузки давят на БД","next":"protect"}]},"scale_path":{"kind":"step","title":"На каком этапе масштабирования вы находитесь: вертикальное, реплики для чтения или шардинг?","detail":"У масштабирования есть правильный порядок. Вертикальное масштабирование (более мощное оборудование) — самый простой первый шаг; затем вы распределяете чтение по репликам; только когда один основной узел больше не справляется с записью, переходите к горизонтальному секционированию (шардингу). Где вы сейчас?","choices":[{"label":"Я ещё не наращивал оборудование / не исчерпал базовую настройку","next":"sol_vertical"},{"label":"Запись помещается на одном экземпляре, но нужно распределить нагрузку чтения","next":"sol_read_replica"},{"label":"Один основной узел больше не справляется с нагрузкой записи","next":"sol_sharding"}]},"protect":{"kind":"step","title":"Как вы защитите базу данных от нагрузки: буферизацией, ограничением или изоляцией?","detail":"Масштабирование — не всегда увеличение БД; часто это защита БД от перегрузки. Вы сгладите внезапные пики записи очередью, ограничите частоту входящих запросов (rate limiting) или не дадите одной зависимости обрушить всю систему (bulkhead/circuit breaker)?","choices":[{"label":"Я хочу сгладить внезапные пики записи","next":"sol_queue"},{"label":"Я хочу ограничить/справедливо распределить частоту входящих запросов","next":"sol_ratelimit"},{"label":"Я не хочу, чтобы одна отказавшая зависимость обрушила всю систему","next":"sol_bulkhead"}]},"sol_vertical":{"kind":"solution","title":"Рекомендация: сначала вертикальное масштабирование и базовая настройка","detail":"Самый дешёвый и наименее рискованный шаг — вертикальное масштабирование: добавить CPU/RAM/более быстрые диски и исправить основы (индексы, пул соединений, параметры памяти). Исчерпайте эти выигрыши до сложных решений, таких как шардинг.","steps":["Первый шаг: Нарастите оборудование (CPU/RAM/NVMe) и настройте параметры буферной/кэш-памяти под нагрузку.","Дешёвые выигрыши: Добавьте недостающие индексы, исправьте тяжёлые запросы, поставьте перед БД пул соединений (PgBouncer/ProxySQL); большинство проблем «масштаба» на самом деле проблемы настройки.","Когда этого достаточно: Если наращённый единственный экземпляр обеспечивает целевую задержку/пропускную способность, остановитесь; не добавляйте лишней сложности.","Внимание: У вертикального масштабирования есть физический и стоимостной потолок; по мере приближения к нему переходите к репликам для чтения и кэшированию.","Далее: Если преобладает чтение — реплики + кэш; если преобладает запись и потолок превышен — шардинг."],"related":["decide-capacity","high-cpu"]},"sol_read_replica":{"kind":"solution","title":"Рекомендация: распределение чтения с помощью реплик для чтения","detail":"Запись остаётся на одном основном узле, а трафик только на чтение распределяется по одной или нескольким репликам. Это намного проще шардинга и решает большинство нагрузок с преобладанием чтения; основной узел остаётся только для записи и чтений, критичных к согласованности.","steps":["Топология: Основной узел (запись) → несколько асинхронных реплик для чтения; чтение распределяется по репликам через балансировщик нагрузки.","Движки: Потоковая репликация PostgreSQL, реплики MySQL, управляемые реплики для чтения.","Когда: Соотношение чтения/записи сильно смещено в сторону чтения, а запись ещё может обслуживаться одним экземпляром.","Внимание: Отставание репликации приводит к чтению устаревших данных; чтение, которому нужен read-your-writes, обслуживайте с основного узла.","Далее: Если даже реплик недостаточно или настоящее узкое место — запись, добавьте кэш, затем рассмотрите шардинг."],"related":["replication-lag","decide-shard-replica"]},"sol_sharding":{"kind":"solution","title":"Рекомендация: шардинг (горизонтальное секционирование) как крайняя мера","detail":"Когда один основной узел не справляется с нагрузкой записи, данные делятся по ключу на несколько независимых частей (шардов); каждый шард берёт на себя часть записи. Это мощно, но трудно обратимо; усложняет межшардовые запросы и транзакции, поэтому оставляется напоследок.","steps":["Ключ шардинга: Выберите ключ, который равномерно распределяет нагрузку и данные и позволяет большинству запросов попадать в один шард (например, customer_id).","Метод: Секционирование по хешу (равномерное распределение) или по диапазону (простые запросы по диапазону, но риск горячих точек).","Примеры движков: Шардинг MongoDB, Citus (PostgreSQL), Vitess (MySQL) или шардинг на уровне приложения.","Внимание: Межшардовые соединения/транзакции дороги и сложны; проектируйте схему так, чтобы большинство транзакций завершались в пределах одного шарда.","Внимание: Неверный ключ создаёт горячие точки (нагрузка скапливается на одном шарде); избегайте монотонных ключей, таких как возрастающие id/даты, и помните, что решардинг сложен."],"related":["decide-shard-replica","decide-partition"]},"sol_cache":{"kind":"solution","title":"Рекомендация: паттерны кэширования (cache-aside + write-back)","detail":"При высоком трафике с преобладанием чтения перед БД ставится кэш в памяти (Redis), и большинство чтений обслуживается, вообще не доходя до БД. Для большого объёма записи write-back буферизует записи и сбрасывает их в БД пакетами, разгружая основной узел.","steps":["Cache-aside: Приложение сначала проверяет Redis; при промахе получает данные из БД и записывает их в кэш. Это снимает с БД большую часть нагрузки чтения.","Write-back (write-behind): Для очень большого объёма записи, например счётчиков/метрик, сначала пишите в кэш и сбрасывайте в БД пакетами в фоне.","TTL + инвалидация: Задавайте ключам разумный TTL; при записи обновляйте/удаляйте связанный ключ, чтобы не оставалось устаревших данных.","Внимание: Cache stampede: когда истекает популярный ключ, тысячи запросов одновременно обрушиваются на БД; используйте блокировку/single-flight, раннее обновление и TTL со случайным разбросом (jitter).","Внимание: При write-back, если узел кэша падает до записи в БД, данные теряются; защищайте критичные данные надёжной очередью/AOF."],"related":["decide-cache","cache-hit-low"]},"sol_queue":{"kind":"solution","title":"Рекомендация: буферизация с помощью очереди (backpressure)","detail":"Чтобы внезапные пики записи не били напрямую по БД, между ними ставится надёжная очередь (например, Kafka/очередь сообщений), и записи потребляются с той скоростью, которую выдерживает БД. Очередь поглощает пик; вместо синхронного отказа система работает с контролируемой задержкой (backpressure).","steps":["Принцип: Производитель пишет событие в очередь; потребитель пишет в БД с устойчивой для неё скоростью (возможно, пакетами).","Движок: Kafka или очередь сообщений; служит буфером, изолирующим БД от пикового трафика.","Backpressure: Если очередь продолжает расти, замедляйте производителя/сигнализируйте ему; не допускайте неограниченного накопления, иначе задержка резко возрастёт.","Когда: Трафик записи идёт сильными всплесками, а немедленная синхронная обработка не обязательна (асинхронная обработка допустима).","Внимание: Очередь вносит асинхронность; результат согласован в конечном счёте. Сделайте потребителя идемпотентным (сообщения могут доставляться повторно)."],"related":["connection-storm"]},"sol_ratelimit":{"kind":"solution","title":"Рекомендация: rate limiting (ограничение частоты запросов)","detail":"Ограничивая входящие запросы по источнику (rate limiting), вы предотвращаете и злоупотребления, и захват всех ресурсов одним клиентом. Ограничение нагрузки, доходящей до БД, делает систему предсказуемой и справедливой.","steps":["Размещение: Применяйте ограничение до БД (API gateway / граница приложения); до БД должны доходить только принятые запросы.","Метод: Лимит на клиента или пользователя с token bucket / sliding window; при превышении возвращайте 429.","Справедливое распределение: Используйте квоты, чтобы «шумный сосед» (noisy neighbor) не поглотил всю ёмкость.","Когда: Трафик может внезапно вырасти или есть риск злоупотреблений/автоматизированных клиентов.","Внимание: Rate limiting — защитная мера, а не масштабирование; если есть реальная потребность в ёмкости, устраните и само узкое место (вертикальное/реплика/шард)."],"related":["connection-storm"]},"sol_bulkhead":{"kind":"solution","title":"Рекомендация: bulkhead + circuit breaker (изоляция)","detail":"Чтобы зависимость (например, замедляющаяся БД или внешний сервис) не обрушила всю систему, ресурсы разделяются на отсеки (bulkheads), а вызовы к постоянно отказывающей зависимости временно прекращаются (circuit breaker). Так частичный сбой не распространяется на всю систему.","steps":["Bulkhead: Выделяйте отдельные пулы соединений/потоки для разных видов работы; один медленный путь не должен исчерпать весь пул и лишить ресурсов остальные.","Circuit breaker: Когда зависимость превышает порог ошибок/задержки, «размыкайте» цепь и на время быстро возвращайте отказ; затем проверяйте восстановление пробными вызовами (half-open).","Тайм-ауты: Задавайте разумный тайм-аут для каждого вызова БД/сервиса; зависшие вызовы не должны блокировать пул.","Когда: В системе с множеством зависимостей, где замедление одного компонента несёт риск каскадного отказа.","Внимание: Спроектируйте плавную деградацию (graceful degradation) на случай разомкнутой цепи: возвращайте устаревшие данные из кэша или частичный результат вместо отклонения всего запроса."],"related":["connection-storm","high-cpu"]}}},"types/types-functions.json":{"schemaVersion":1,"sections":[{"id":"datatypes","title":"Типы данных","icon":"🔤","rows":[{"id":"var-text","concept":"Текст переменной длины","engines":{"oracle":"VARCHAR2(n)","postgres":"VARCHAR(n) / TEXT","mysql":"VARCHAR(n)","mssql":"NVARCHAR(n)","mongodb":"string","redis":"string (value)","elasticsearch":"text / keyword"},"note":"В ES text используется для полнотекстового поиска, а keyword — для точного совпадения/агрегаций."},{"id":"fixed-text","concept":"Текст фиксированной длины","engines":{"oracle":"CHAR(n)","postgres":"CHAR(n)","mysql":"CHAR(n)","mssql":"NCHAR(n)","mongodb":"string","redis":"string","elasticsearch":"keyword"},"note":"В документных/key-value системах нет понятия фиксированной длины; значения хранятся как строки."},{"id":"large-text","concept":"Большой текст (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":"Целое число (32-бит)","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 нет отдельного типа INT; используется NUMBER."},{"id":"int64","concept":"Большое целое (64-бит)","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":"Десятичное/точное число (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":"Для точных значений, например денег, эти типы предпочтительнее float."},{"id":"float","concept":"С плавающей точкой (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":"Логический (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":"Тип столбца BOOLEAN появился в Oracle SQL только в 23c; до этого он имитируется с помощью NUMBER/CHAR."},{"id":"date","concept":"Дата","engines":{"oracle":"DATE (includes time)","postgres":"DATE","mysql":"DATE","mssql":"DATE","mongodb":"Date (ISODate)","redis":"none (epoch string/number)","elasticsearch":"date (with format)"},"note":"Тип Oracle DATE хранит и дату, и время; это не только дата."},{"id":"datetime","concept":"Дата-время (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":"Дата-время с часовым поясом","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 и ES хранят даты в UTC; часовой пояс применяется при отображении."},{"id":"binary","concept":"Двоичные данные (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 бинарно-безопасны и могут хранить любую последовательность байтов."},{"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":"Собственный _id в Mongo — ObjectId; UUID также можно хранить как 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":"В Postgres для запросов/индексирования предпочтителен JSONB; MongoDB изначально документная."},{"id":"auto-increment","concept":"Автоинкремент (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/фиксированный список","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":"Массив","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":"В ES любое поле может неявно содержать массив; отдельного типа массива нет."},{"id":"ip-address","concept":"IP-адрес","engines":{"oracle":"VARCHAR2 / UTL_INADDR","postgres":"INET / CIDR","mysql":"VARBINARY(16) / INET6_ATON","mssql":"none, via VARCHAR","mongodb":"none, stored as a string","redis":"none, stored as a string","elasticsearch":"ip"},"note":"Встроенные IP-типы в Postgres и ES поддерживают запросы по диапазону/подсети."},{"id":"money","concept":"Деньги","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":"Чтобы избежать ошибок округления, деньги хранятся в DECIMAL/Decimal128, а не во float."},{"id":"spatial","concept":"Географические/пространственные","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":"Общие функции","icon":"🧩","rows":[{"id":"coalesce","concept":"Значение по умолчанию при 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":"Замена при 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":"Текущие дата и время (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":"Текущая дата","engines":{"oracle":"TRUNC(SYSDATE) / CURRENT_DATE","postgres":"CURRENT_DATE","mysql":"CURDATE()","mssql":"CAST(GETDATE() AS DATE)","mongodb":"$dateTrunc / new Date()","redis":"TIME (epoch)","elasticsearch":"now/d"}},{"id":"concat","concept":"Конкатенация текста (concat)","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":"В MySQL || по умолчанию означает OR; для конкатенации используйте CONCAT."},{"id":"substring","concept":"Подстрока","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":"Длина строки","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":"Верхний/нижний регистр","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)","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)","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":"Преобразование типа (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":"Форматирование даты в текст (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":"Преобразование текста в дату (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":"Прибавление к дате (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":"Разница между двумя датами (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)","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)","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)","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":"Условие (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)","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":"Конкатенация в группе (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)","engines":{"oracle":"ROW_NUMBER() OVER (...)","postgres":"ROW_NUMBER() OVER (...)","mysql":"ROW_NUMBER() OVER (...)","mssql":"ROW_NUMBER() OVER (...)","mongodb":"$setWindowFields: { $documentNumber }","redis":"none","elasticsearch":"none (top_hits / bucket_sort)"}},{"id":"limit","concept":"Первые N строк (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":"Текущий пользователь","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":"Генерация 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":"Извлечение поля JSON","engines":{"oracle":"JSON_VALUE(j, '$.a')","postgres":"j->>'a' / j #>> '{a}'","mysql":"JSON_EXTRACT(j, '$.a') / j->>'$.a'","mssql":"JSON_VALUE(j, '$.a')","mongodb":"doc.a (native access)","redis":"JSON.GET key $.a (RedisJSON)","elasticsearch":"doc['a'] / field access (native)"}},{"id":"regexp","concept":"Совпадение с регулярным выражением (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 (и Azure SQL) добавлены встроенные функции регулярных выражений, такие как REGEXP_LIKE, REGEXP_REPLACE и REGEXP_SUBSTR (для REGEXP_LIKE нужен уровень совместимости 170); в более ранних версиях требуется LIKE или функция CLR."}]}]},"reference/params-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"params":[{"name":"sga_target","default":"0","recommended":"~60% RAM на выделенном сервере","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Задаёт общий размер SGA для Automatic Shared Memory Management (ASMM); автоматически распределяет подпулы.","tags":["memory","memory","sga"]},{"name":"sga_max_size","default":"Same as sga_target","recommended":"Равен sga_target или немного больше","unit":"bytes (K/M/G)","dynamic":"restart","desc":"Задаёт верхний предел, до которого SGA может расти во время работы; его увеличение требует перезапуска экземпляра.","tags":["memory","memory","sga"]},{"name":"pga_aggregate_target","default":"10M or 20% of the SGA","recommended":"~20% RAM","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Задаёт общий целевой объём памяти PGA, используемой всеми серверными процессами; влияет на операции сортировки и хеширования.","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":"Обычно достаточно значения по умолчанию","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Жёсткий верхний предел памяти PGA для всего экземпляра; при превышении прерываются вызовы сеансов, потребляющих больше всего (кроме SYS и фоновых процессов). Появился в Oracle 12c.","tags":["memory","memory","pga","limit"]},{"name":"memory_target","default":"0","recommended":"0 (предпочтительно раздельное управление SGA/PGA)","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Объединяет SGA и PGA под одной целью с Automatic Memory Management (AMM); не рекомендуется на крупных системах.","tags":["memory","memory","amm"]},{"name":"db_cache_size","default":"0 (automatic)","recommended":"Оставьте 0, если используется sga_target","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Задает размер буферного кэша для стандартного размера блока; удерживает блоки данных в памяти.","tags":["memory","memory","cache","buffer"]},{"name":"shared_pool_size","default":"0 (automatic)","recommended":"Оставьте 0, если используется sga_target","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Размер разделяемого пула, в котором хранятся результаты разбора SQL/PLSQL, кэш словаря и библиотечный кэш.","tags":["memory","memory","shared","pool"]},{"name":"processes","default":"100 / 300 (depending on version)","recommended":"Увеличивайте исходя из ожидаемого числа одновременных подключений","unit":"integer","dynamic":"restart","desc":"Максимальное число процессов операционной системы, которые могут подключаться к экземпляру; изменение требует перезапуска.","tags":["connection","connection","process","process"]},{"name":"sessions","default":"Derived from processes (1.5x+22)","recommended":"Обычно достаточно значения, вычисляемого по умолчанию","unit":"integer","dynamic":"restart","desc":"Максимальное число одновременно существующих сессий; вычисляется автоматически из значения processes.","tags":["connection","connection","session","session"]},{"name":"open_cursors","default":"50","recommended":"300-1000 в зависимости от потребностей приложения","unit":"integer","dynamic":"dynamic","desc":"Задает максимальное число курсоров, которые сессия может одновременно держать открытыми.","tags":["cursor","cursor","session","session"]},{"name":"cursor_sharing","default":"EXACT","recommended":"EXACT (используйте bind-переменные)","unit":"EXACT | FORCE","dynamic":"dynamic","desc":"Определяет, разделяются ли SQL-операторы с литералами через bind-переменные; FORCE заменяет литералы.","tags":["cursor","cursor","sql","parse"]},{"name":"optimizer_mode","default":"ALL_ROWS","recommended":"ALL_ROWS","unit":"ALL_ROWS | FIRST_ROWS_n","dynamic":"dynamic","desc":"Задает цель стоимостного оптимизатора: приоритет общей пропускной способности или времени отклика на первую строку.","tags":["optimizer","optimize","plan","sql"]},{"name":"undo_retention","default":"900","recommended":"По длительности самого долгого запроса/flashback","unit":"seconds","dynamic":"dynamic","desc":"Задает, как долго по возможности хранятся зафиксированные данные undo; влияет на риск ORA-01555.","tags":["undo","rollback","retention","flashback"]},{"name":"undo_tablespace","default":"UNDOTBS1","recommended":"Выделенное табличное пространство undo достаточного размера","unit":"string (tablespace name)","dynamic":"dynamic","desc":"Задает активное табличное пространство undo для автоматического управления undo; можно менять онлайн.","tags":["undo","rollback","tablespace"]},{"name":"db_recovery_file_dest_size","default":"0","recommended":"Достаточно для archivelog + бэкапов + flashback-журналов","unit":"bytes (K/M/G)","dynamic":"dynamic","desc":"Задает максимальный размер Fast Recovery Area (FRA) на диске; там хранятся архивные журналы и бэкапы.","tags":["recovery","recovery","fra","archive"]},{"name":"db_writer_processes","default":"1 or based on CPU count","recommended":"Около CPU/8 (увеличьте при высокой нагрузке на запись)","unit":"integer","dynamic":"restart","desc":"Задает число процессов database writer (DBWn), записывающих грязные блоки на диск; распараллеливает нагрузку записи.","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":"Задавайте исходя из нагрузки и числа CPU","unit":"integer","dynamic":"dynamic","desc":"Ограничивает максимальное число серверов параллельного выполнения (PX), одновременно работающих в экземпляре.","tags":["parallel","parallel","px","query"]},{"name":"statistics_level","default":"TYPICAL","recommended":"TYPICAL","unit":"BASIC | TYPICAL | ALL","dynamic":"dynamic","desc":"Задает уровень AWR/ADDM и автоматического сбора статистики; BASIC отключает многие средства диагностики.","tags":["statistics","statistics","awr","monitoring"]},{"name":"deferred_segment_creation","default":"TRUE","recommended":"TRUE (экономит место на пустых таблицах)","unit":"TRUE | FALSE","dynamic":"dynamic","desc":"Откладывает создание первого сегмента пустой таблицы/индекса до вставки первой строки; экономит место.","tags":["segment","deferred-segment","space","storage"]},{"name":"audit_trail","default":"NONE (DB in databases created with DBCA)","recommended":"Используйте DB или unified auditing","unit":"NONE | DB | DB,EXTENDED | OS | XML","dynamic":"restart","desc":"Определяет, где и как записываются традиционные записи аудита; изменение требует перезапуска.","tags":["audit","audit","security","security"]},{"name":"compatible","default":"Installed version (e.g., 19.0.0)","recommended":"Повышайте, когда возможности новой версии стабильны","unit":"version string","dynamic":"restart","desc":"Определяет, какие возможности версии включены; после повышения понизить нельзя, требует перезапуска.","tags":["compatible","compatibility","version","version"]},{"name":"db_block_size","default":"8192","recommended":"8K (OLTP); задается только при создании","unit":"bytes","dynamic":"restart","desc":"Задает стандартный размер блока базы данных; задается только при создании базы данных и впоследствии не меняется.","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":"Управляет использованием асинхронного и прямого ввода-вывода для файлов данных на файловых системах.","tags":["io","io","async","directio"]},{"name":"log_archive_max_processes","default":"4","recommended":"Увеличивайте исходя из числа направлений архивации","unit":"integer","dynamic":"dynamic","desc":"Задает максимальное число процессов ARCn, архивирующих redo-журналы; увеличивается для снижения отставания архивации.","tags":["archive","archive","redo","log"]}]},"reference/params-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"params":[{"name":"shared_buffers","default":"128MB","recommended":"~25% RAM","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Объем разделяемой памяти, который PostgreSQL выделяет для кэширования блоков данных; изменение требует перезапуска.","tags":["memory","memory","cache","buffer"]},{"name":"effective_cache_size","default":"4GB","recommended":"~50-75% RAM","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Сообщает планировщику оценку общего размера кэша ОС + PostgreSQL; влияет на использование индексов.","tags":["memory","memory","planner","cache"]},{"name":"work_mem","default":"4MB","recommended":"16-64MB (учитывайте число подключений)","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Память, которую операции сортировки и хеширования могут использовать до сброса на диск; выделяется на каждый узел плана запроса.","tags":["memory","memory","sort","query"]},{"name":"maintenance_work_mem","default":"64MB","recommended":"256MB-1GB","unit":"bytes (kB/MB/GB)","dynamic":"dynamic","desc":"Задает максимальный объем памяти для операций обслуживания, таких как VACUUM, CREATE INDEX и ALTER TABLE.","tags":["memory","memory","vacuum","maintenance"]},{"name":"max_connections","default":"100","recommended":"100-200 (свыше используйте пулер)","unit":"integer","dynamic":"restart","desc":"Максимальное число принимаемых одновременных клиентских подключений; изменение требует перезапуска.","tags":["connection","connection","limit"]},{"name":"wal_buffers","default":"-1 (1/32 of shared_buffers)","recommended":"16MB","unit":"bytes (kB/MB/GB)","dynamic":"restart","desc":"Задает размер буфера разделяемой памяти, где хранятся записи WAL до записи на диск. Значение -1 (автоматически) ограничено минимум 64kB и максимум одним сегментом WAL (обычно 16MB).","tags":["wal","memory","memory","buffer"]},{"name":"max_wal_size","default":"1GB","recommended":"4-16GB в зависимости от нагрузки","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Задает мягкий верхний предел роста WAL до запуска автоматической контрольной точки.","tags":["wal","checkpoint","disk"]},{"name":"min_wal_size","default":"80MB","recommended":"1-2GB","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Нижний предел, до которого файлы WAL сохраняются для повторного использования; снижает частое перераспределение.","tags":["wal","checkpoint","disk"]},{"name":"checkpoint_completion_target","default":"0.9","recommended":"0.9","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Задает долю интервала контрольной точки, на которую распределяется запись; сглаживает пики I/O. В PG14 значение по умолчанию стало 0.9; раньше было 0.5.","tags":["checkpoint","io","wal"]},{"name":"checkpoint_timeout","default":"5min","recommended":"15-30min","unit":"time (s/min)","dynamic":"dynamic","desc":"Задает максимальное время между автоматическими контрольными точками; увеличение снижает нагрузку записи, но удлиняет восстановление.","tags":["checkpoint","wal","recovery"]},{"name":"random_page_cost","default":"4.0","recommended":"1.1 для SSD","unit":"decimal","dynamic":"dynamic","desc":"Задает относительную стоимость случайного доступа к диску для планировщика; снижение на SSD поощряет индексное сканирование.","tags":["planner","optimize","io","index"]},{"name":"effective_io_concurrency","default":"1","recommended":"200 для SSD","unit":"integer","dynamic":"dynamic","desc":"Оценочное число одновременных операций I/O, которые диск может обслужить; влияет на упреждающее чтение в bitmap-сканированиях. По умолчанию 1 в PG17 и ранее, 16 в PG18.","tags":["io","gc","ssd","prefetch"]},{"name":"default_statistics_target","default":"100","recommended":"100 (повышайте для отдельных столбцов на больших таблицах)","unit":"integer","dynamic":"dynamic","desc":"Задает детальность статистики, собираемой ANALYZE; повышение улучшает точность планов и увеличивает время analyze.","tags":["statistics","statistics","analyze","planner"]},{"name":"autovacuum","default":"on","recommended":"on","unit":"on | off","dynamic":"dynamic","desc":"Включает автоматический процесс vacuum, удаляющий мертвые кортежи и собирающий статистику; отключение ведет к bloat.","tags":["autovacuum","vacuum","maintenance","bloat"]},{"name":"autovacuum_vacuum_scale_factor","default":"0.2","recommended":"0.05-0.1 на больших таблицах","unit":"ratio (0-1)","dynamic":"dynamic","desc":"Задает долю строк таблицы, которая должна измениться до запуска vacuum; снижение чаще очищает большие таблицы.","tags":["autovacuum","vacuum","bloat","maintenance"]},{"name":"autovacuum_max_workers","default":"3","recommended":"3-6 (повышайте на системах с большим числом таблиц)","unit":"integer","dynamic":"restart","desc":"Задает максимальное число одновременно работающих рабочих процессов autovacuum; в PG17 и ранее изменение требует перезапуска. В PG18 можно менять через reload в пределах autovacuum_worker_slots.","tags":["autovacuum","vacuum","worker","maintenance"]},{"name":"synchronous_commit","default":"on","recommended":"on (критичные данные); off (приоритет производительности)","unit":"on | off | local | remote_write | remote_apply","dynamic":"dynamic","desc":"Определяет, ждет ли commit записи WAL на диск; off несет риск небольшой потери данных при сбое.","tags":["wal","commit","durability","performance"]},{"name":"wal_level","default":"replica","recommended":"replica (для репликации/PITR)","unit":"minimal | replica | logical","dynamic":"restart","desc":"Определяет, сколько информации записывается в WAL; необходим для репликации и логической репликации, изменение требует перезапуска.","tags":["wal","replication","replication","pitr"]},{"name":"max_wal_senders","default":"10","recommended":"По числу standby + бэкапов","unit":"integer","dynamic":"restart","desc":"Задает максимальное число процессов, одновременно передающих WAL (для standby и бэкапов).","tags":["wal","replication","replication","standby"]},{"name":"wal_keep_size","default":"0","recommended":"Достаточно, чтобы покрыть отставание standby","unit":"bytes (MB/GB)","dynamic":"dynamic","desc":"Минимальный объем WAL, хранимый в pg_wal для standby; защищает отстающий standby при отсутствии слота репликации. Появился в PG13; до этого использовался wal_keep_segments (число сегментов).","tags":["wal","replication","replication","standby"]},{"name":"log_min_duration_statement","default":"-1 (disabled)","recommended":"1000ms для отлова медленных запросов","unit":"milliseconds","dynamic":"dynamic","desc":"Журналирует операторы, выполняющиеся дольше заданного времени; используется для выявления медленных запросов, -1 отключает.","tags":["logging","log","slow-query","slow-query"]},{"name":"idle_in_transaction_session_timeout","default":"0 (disabled)","recommended":"60000ms для предотвращения удерживаемых блокировок","unit":"milliseconds","dynamic":"dynamic","desc":"Завершает сессии, простаивающие внутри открытой транзакции заданное время; снижает риск блокировок и bloat.","tags":["timeout","transaction","transaction","lock"]},{"name":"statement_timeout","default":"0 (disabled)","recommended":"Зависит от приложения (низкий для OLTP)","unit":"milliseconds","dynamic":"dynamic","desc":"Автоматически отменяет операторы, превысившие заданное время; не дает неконтролируемым запросам расходовать ресурсы.","tags":["timeout","query","query","limit"]},{"name":"shared_preload_libraries","default":"'' (empty)","recommended":"pg_stat_statements (для мониторинга)","unit":"string (comma-separated list)","dynamic":"restart","desc":"Задает разделяемые библиотеки, загружаемые при старте сервера; здесь настраиваются расширения вроде pg_stat_statements.","tags":["extension","extension","preload","monitoring"]},{"name":"max_parallel_workers_per_gather","default":"2","recommended":"2-4 (в зависимости от числа CPU)","unit":"integer","dynamic":"dynamic","desc":"Задает максимальное число параллельных рабочих процессов, которые может запустить один узел Gather/GatherMerge; 0 отключает параллелизм.","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% RAM на выделенном сервере","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Основной пул памяти, в котором InnoDB кэширует данные и индексы; в 8.0 можно менять онлайн, но крупные изменения требуют осторожности.","tags":["memory","memory sizing","innodb","buffer"]},{"name":"innodb_buffer_pool_instances","default":"1 (or 8 if size>1GB)","recommended":"8 для больших пулов","unit":"integer","dynamic":"restart","desc":"Задает число экземпляров, на которые делится buffer pool; снижает конкуренцию за мьютексы, изменение требует перезапуска.","tags":["memory","memory sizing","innodb","concurrency"]},{"name":"innodb_redo_log_capacity","default":"100MB (8.0.30+)","recommended":"1-8GB (в зависимости от нагрузки записи; ~1 час redo в пике)","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Задает общий объем диска, занимаемый redo log InnoDB; увеличение сглаживает давление контрольных точек и пиковую нагрузку записи. Появился в MySQL 8.0.30 и меняется без перезапуска через SET GLOBAL/SET PERSIST. Старые innodb_log_file_size (по умолчанию 48MB) и innodb_log_files_in_group устарели с 8.0.30; до 8.0.30 размер redo задается этими двумя параметрами и требует перезапуска.","tags":["innodb","redo","log","io"]},{"name":"innodb_flush_log_at_trx_commit","default":"1","recommended":"1 (полный ACID); 2 (производительность)","unit":"0 | 1 | 2","dynamic":"dynamic","desc":"Управляет записью и синхронизацией redo log на диск при каждом commit; 1 дает полную надежность, 2/0 несут риск потерь.","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":"Определяет, как InnoDB сбрасывает файлы данных и журналов на диск; O_DIRECT исключает двойную буферизацию, изменение требует перезапуска.","tags":["innodb","io","flush","gc"]},{"name":"innodb_io_capacity","default":"8.0: 200 / 8.4: 10000","recommended":"По реальным IOPS хранилища (1000-2000 для SSD на 8.0)","unit":"integer (IOPS)","dynamic":"dynamic","desc":"Задает оценочную IOPS-емкость диска для фоновых задач InnoDB (flush, merge). В MySQL 8.4 значения по умолчанию повышены до innodb_io_capacity=10000 и innodb_io_capacity_max=20000 (200/2000 в 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 (в зависимости от нагрузки чтения)","unit":"integer","dynamic":"restart","desc":"Задает число фоновых потоков InnoDB, обрабатывающих запросы чтения I/O; изменение требует перезапуска.","tags":["innodb","io","thread","read"]},{"name":"innodb_write_io_threads","default":"4","recommended":"4-8 (в зависимости от нагрузки записи)","unit":"integer","dynamic":"restart","desc":"Задает число фоновых потоков InnoDB, обрабатывающих запросы записи I/O; изменение требует перезапуска.","tags":["innodb","io","thread","write"]},{"name":"max_connections","default":"151","recommended":"200-500 в зависимости от нагрузки","unit":"integer","dynamic":"dynamic","desc":"Задает максимальное допустимое число одновременных клиентских подключений; можно менять онлайн.","tags":["connection","connections","limit"]},{"name":"thread_cache_size","default":"-1 (autosized)","recommended":"Автоматически; увеличьте при множестве короткоживущих подключений","unit":"integer","dynamic":"dynamic","desc":"Задает, сколько потоков хранится в кэше для повторного использования; сокращает создание потоков для новых подключений.","tags":["connection","connections","thread","cache"]},{"name":"table_open_cache","default":"4000","recommended":"4000-8000 в зависимости от числа открытых таблиц","unit":"integer","dynamic":"dynamic","desc":"Задает размер кэша дескрипторов открытых таблиц для всех потоков; снижает накладные расходы на повторное открытие таблиц.","tags":["cache","table","table cache","open"]},{"name":"tmp_table_size","default":"16MB","recommended":"64-256MB","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Задает максимальный размер временной таблицы в памяти до ее преобразования в таблицу на диске; работает вместе с max_heap_table_size.","tags":["memory","memory sizing","temp","temporary table"]},{"name":"max_heap_table_size","default":"16MB","recommended":"Равно tmp_table_size","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Задает максимальный размер, до которого могут расти таблицы движка MEMORY и временные таблицы в памяти.","tags":["memory","memory sizing","temp","heap"]},{"name":"sort_buffer_size","default":"256KB","recommended":"256KB-2MB (учитывайте стоимость на подключение)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Буфер, выделяемый на сессию для сортировок ORDER BY и GROUP BY; слишком большое значение расходует память.","tags":["memory","memory sizing","sort","buffer"]},{"name":"join_buffer_size","default":"256KB","recommended":"256KB-1MB (лучше решать индексом)","unit":"bytes (K/M)","dynamic":"dynamic","desc":"Буфер, выделяемый на сессию для соединений, которые не могут использовать индекс; надежное решение — добавить подходящий индекс.","tags":["memory","memory sizing","join","buffer"]},{"name":"sync_binlog","default":"1","recommended":"1 (полная надежность); 100-1000 при высокой нагрузке","unit":"integer","dynamic":"dynamic","desc":"Задает, после скольких commit бинарный журнал синхронизируется на диск; 1 надежнее всего, большие значения жертвуют надежностью ради производительности.","tags":["binlog","replication","durability","commit"]},{"name":"binlog_expire_logs_seconds","default":"2592000 (30 days)","recommended":"По окну бэкапа/репликации","unit":"seconds","dynamic":"dynamic","desc":"Задает, как долго хранятся файлы бинарного журнала до автоматической очистки; контролирует использование диска.","tags":["binlog","log","retention","replication"]},{"name":"long_query_time","default":"10","recommended":"1 (отлов медленных запросов)","unit":"seconds","dynamic":"dynamic","desc":"Задает длительность, которую запрос должен превысить, чтобы попасть в журнал медленных запросов; принимает дробные значения.","tags":["slow-query","slow-query-log","log","performance"]},{"name":"slow_query_log","default":"OFF","recommended":"ON (для диагностики)","unit":"ON | OFF","dynamic":"dynamic","desc":"Управляет записью запросов, превышающих long_query_time, в журнал медленных запросов; включайте для анализа производительности.","tags":["slow-query","slow-query-log","log","monitoring"]},{"name":"wait_timeout","default":"28800 (8 hours)","recommended":"По пулу приложения (например, 600)","unit":"seconds","dynamic":"dynamic","desc":"Задает максимальное время простоя неинтерактивного подключения до его закрытия; очищает простаивающие подключения.","tags":["timeout","connection","connections","idle"]},{"name":"max_allowed_packet","default":"64MB","recommended":"64-256MB для больших BLOB/строк","unit":"bytes (M/G)","dynamic":"dynamic","desc":"Задает максимальный размер одного пакета или любой сгенерированной/промежуточной строки; увеличивайте для передачи больших BLOB.","tags":["packet","packet size","blob","network"]},{"name":"innodb_lock_wait_timeout","default":"50","recommended":"10-50 для OLTP","unit":"seconds","dynamic":"dynamic","desc":"Задает, сколько транзакция InnoDB ждет блокировку строки до истечения тайм-аута.","tags":["innodb","lock","lock wait","timeout"]},{"name":"character_set_server","default":"utf8mb4","recommended":"utf8mb4","unit":"text (character set)","dynamic":"dynamic","desc":"Задает набор символов сервера по умолчанию; utf8mb4 обеспечивает полную поддержку Unicode (включая эмодзи).","tags":["charset","character-set","unicode","utf8mb4"]}]},"reference/params-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"params":[{"name":"max server memory (MB)","default":"2147483647","recommended":"Весь RAM минус доля для ОС и других служб (~10-25%)","unit":"MB","dynamic":"dynamic","desc":"Задает максимальный объем памяти, который могут использовать buffer pool SQL Server и другие потребители памяти. По умолчанию фактически не ограничен; если на сервере работают другие службы, его нужно снизить, чтобы оставить достаточно памяти операционной системе. Задается через sp_configure; вступает в силу сразу с RECONFIGURE.","tags":["memory","memory limit","buffer pool"]},{"name":"min server memory (MB)","default":"0","recommended":"На критичных экземплярах — разумная нижняя граница относительно max","unit":"MB","dynamic":"dynamic","desc":"Минимальный объем памяти, который SQL Server после выделения не возвращает операционной системе. Используется, чтобы SQL Server не освобождал слишком много памяти при нехватке памяти. Задается через sp_configure; вступает в силу с RECONFIGURE.","tags":["memory","memory limit"]},{"name":"max degree of parallelism","default":"0","recommended":"Обычно 8 или число физических ядер на узел NUMA","unit":"count","dynamic":"dynamic","desc":"Ограничивает максимальное число процессоров (ядер), которое один запрос может использовать при параллельном выполнении. 0 означает, что можно использовать все ядра; ограничивается, чтобы избежать избыточного параллелизма в OLTP-нагрузках. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","maxdop","cpu"]},{"name":"cost threshold for parallelism","default":"5","recommended":"25-50 на современном оборудовании","unit":"cost","dynamic":"dynamic","desc":"Минимальный порог оценочной стоимости, которого должен достичь план запроса для параллельного выполнения. Значение по умолчанию 5 очень низкое и приводит к ненужному распараллеливанию мелких запросов; повышение улучшает поведение параллелизма на всем сервере. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["parallelism","parallel query","cpu"]},{"name":"max worker threads","default":"0","recommended":"0 (пусть SQL Server вычислит сам)","unit":"count","dynamic":"dynamic","desc":"Задает максимальное число рабочих потоков, которые SQL Server может создать для обработки клиентских запросов. 0 означает автоматический расчет по числу процессоров и архитектуре. Это расширенный параметр; применяется через sp_configure + RECONFIGURE без перезапуска (sys.configurations.is_dynamic = 1).","tags":["threads","worker","cpu","concurrency"]},{"name":"optimize for ad hoc workloads","default":"0","recommended":"1 (вкл.)","unit":"0/1","dynamic":"dynamic","desc":"Для ad hoc-запросов, выполняемых однократно, при первом выполнении кэширует небольшую заглушку скомпилированного плана вместо полного плана. Не дает кэшу планов заполняться одноразовыми планами и улучшает использование памяти. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["plan cache","memory","memory usage"]},{"name":"backup compression default","default":"0","recommended":"1 (вкл.)","unit":"0/1","dynamic":"dynamic","desc":"Делает новые бэкапы сжатыми по умолчанию. Сжатие значительно уменьшает размер и длительность бэкапа ценой некоторой нагрузки на CPU. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["backup","backup size","compression","backup compression"]},{"name":"fill factor (%)","default":"0","recommended":"0 или 90-100 (в зависимости от нагрузки)","unit":"%","dynamic":"restart","desc":"Задает в процентах степень заполнения страниц листового уровня при создании или перестроении (REBUILD) индекса. 0 и 100 заполняют страницы полностью; меньшие значения сокращают разбиения страниц, но увеличивают расход диска и памяти. Значение по умолчанию на уровне сервера меняется через sp_configure, но не вступает в силу до перезапуска службы (is_dynamic = 0); параметр FILLFACTOR для отдельного индекса можно использовать сразу.","tags":["index","page split","storage","disk usage"]},{"name":"recovery interval (min)","default":"0","recommended":"0 (автоматически)","unit":"minutes","dynamic":"dynamic","desc":"Задает частоту автоматических контрольных точек через целевое максимальное время восстановления. 0 позволяет SQL Server автоматически управлять целевым временем восстановления для каждой базы данных. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["checkpoint","recovery","crash recovery"]},{"name":"remote query timeout (s)","default":"600","recommended":"600 или 0 (без ограничения), в зависимости от нагрузки","unit":"seconds","dynamic":"dynamic","desc":"Время, в течение которого сервер ждет ответа на удаленные запросы (через linked server) до истечения тайм-аута. 0 означает ожидание без ограничения. Вступает в силу сразу через sp_configure + RECONFIGURE.","tags":["timeout","remote","linked server"]},{"name":"network packet size (B)","default":"4096","recommended":"4096 (не меняйте без особой необходимости)","unit":"bytes","dynamic":"dynamic","desc":"Размер пакета для передачи данных по сети. Значение по умолчанию рекомендуется, кроме особых массовых операций с передачей очень больших объемов данных; неверная настройка может ухудшить производительность. Вступает в силу через sp_configure + RECONFIGURE.","tags":["network","network packet","packet"]},{"name":"database compatibility level (ALTER DATABASE)","default":"Depends on server version","recommended":"Последний протестированный уровень","unit":"level","dynamic":"dynamic","desc":"Определяет, с поведением и моделью оптимизатора запросов какой версии SQL Server работает база данных. Задается для каждой базы через ALTER DATABASE ... SET COMPATIBILITY_LEVEL и вступает в силу сразу; влияет на возможности оптимизатора, такие как оценка кардинальности.","tags":["database","compatibility level","optimizer","compatibility"]},{"name":"MAXDOP (database scoped configuration)","default":"0 (inherits the server setting)","recommended":"Значение, соответствующее нагрузке базы данных","unit":"count","dynamic":"dynamic","desc":"Переопределяет серверный параметр max degree of parallelism на уровне базы данных. Задается через ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP и вступает в силу сразу; позволяет разное поведение параллелизма для разных баз данных.","tags":["parallelism","parallel query","maxdop","database scoped"]},{"name":"Число файлов tempdb (рекомендация)","default":"SQL Server 2016+ setup: equal to the number of logical cores, at most 8 (1 before)","recommended":"Равно числу логических ядер, не более 8 (при необходимости увеличивайте шагами по 4)","unit":"count","dynamic":"dynamic","desc":"Число файлов данных tempdb важно для снижения конкуренции за страницы распределения. Общая рекомендация — использовать файлы одинакового размера по числу ядер (максимум 8). Файл, добавленный через ALTER DATABASE tempdb ADD FILE, используется сразу без перезапуска; однако если существующие файлы заполнены, выровняйте размеры всех файлов для баланса пропорционального заполнения. Удаление файла tempdb (REMOVE FILE) часто возможно только после перезапуска, так как файл не освобождается.","tags":["tempdb","storage","data files","contention"]},{"name":"Trace Flag 1117 / 1118 (примечание)","default":"Default behavior for tempdb in SQL 2016+","recommended":"Обычно не нужны в SQL 2016+; включены в старых версиях","unit":"flag","dynamic":"restart","desc":"TF 1117 заставляет все файлы файловой группы расти вместе; TF 1118 принудительно включает выделение однородных экстентов вместо смешанных. Начиная с SQL Server 2016 это поведение по умолчанию для tempdb, поэтому флаги обычно не нужны. При добавлении как флага запуска требует перезапуска.","tags":["trace flag","tempdb","note"]},{"name":"Trace Flag 3226","default":"Off","recommended":"Вкл. (подавляет сообщения об успешных бэкапах)","unit":"flag","dynamic":"dynamic","desc":"Не дает записывать успешные операции бэкапа в журнал ошибок SQL Server и журнал событий Windows. На системах с частыми бэкапами журнала не дает журналам заполняться ненужными сообщениями (log spam). Можно включить сразу во время работы через DBCC TRACEON(3226, -1); чтобы сохранить после перезапуска, добавьте также -T3226 как параметр запуска.","tags":["trace flag","backup","backup messages","log"]},{"name":"lock pages in memory (примечание о политике Windows)","default":"Off","recommended":"Предоставляется учетной записи службы в определенных сценариях","unit":"policy","dynamic":"restart","desc":"Политика прав пользователей Windows, позволяющая учетной записи службы SQL Server не допускать выгрузку своих страниц памяти на диск операционной системой. Может снизить колебания производительности при нехватке памяти, но требует аккуратной настройки. Изменение политики требует перезапуска службы.","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":"Оставьте по умолчанию или ~50% доступного RAM","unit":"GB","dynamic":"restart","desc":"Задает в гигабайтах объем памяти, выделяемый внутреннему кэшу движка хранения WiredTiger. По умолчанию используется половина (общий RAM - 1GB). Ручная настройка нужна только в особых случаях и требует перезапуска mongod.","tags":["memory","memory","cache","wiredtiger"]},{"name":"net.maxIncomingConnections","default":"1000000 (effectively limited by OS limits)","recommended":"По нагрузке и ulimit операционной системы","unit":"count","dynamic":"restart","desc":"Ограничивает максимальное число одновременных входящих подключений, которые принимает экземпляр mongod или mongos. Если значение выше лимита файловых дескрипторов/подключений операционной системы (ulimit), оно не действует; фактический потолок определяет ОС (виден как current + available в db.serverStatus().connections). Изменение вносится в файл конфигурации и требует перезапуска.","tags":["network","network","connections","connection"]},{"name":"operationProfiling.slowOpThresholdMs","default":"100","recommended":"100 или меньше в зависимости от нагрузки","unit":"ms","dynamic":"dynamic","desc":"Задает в миллисекундах порог длительности, при превышении которого операция считается медленной и записывается профилировщиком и в журнал. Операции сверх порога записываются для упрощения диагностики. Можно изменить сразу во время работы через db.setProfilingLevel().","tags":["profiling","slow query","slow query","monitoring"]},{"name":"replication.oplogSizeMB","default":"~5% of disk size (max 50GB)","recommended":"Достаточно, чтобы покрыть окно обслуживания","unit":"MB","dynamic":"dynamic","desc":"Задает в мегабайтах максимальный размер коллекции oplog, хранящей операции на членах replica set. Достаточно большой oplog позволяет отстающим членам догнать без полной ресинхронизации. Можно изменить сразу на работающем replica set командой replSetResizeOplog.","tags":["replication","oplog","replica set"]},{"name":"security.authorization","default":"disabled","recommended":"enabled (ролевое управление доступом)","unit":"enabled/disabled","dynamic":"restart","desc":"Включает ролевое управление доступом (RBAC); при включении клиенты должны проходить аутентификацию и авторизацию. В продуктивных средах должно быть всегда включено. Задается в файле конфигурации и требует перезапуска.","tags":["security","security","authorization","authorization","rbac"]},{"name":"net.compression.compressors","default":"snappy,zstd,zlib","recommended":"snappy,zstd (значение по умолчанию подходит)","unit":"list","dynamic":"restart","desc":"Задает алгоритмы сжатия и порядок их предпочтения для сетевого трафика клиентов и внутри кластера. Снижает использование пропускной способности сети ценой некоторой нагрузки на CPU. Задается в файле конфигурации и требует перезапуска.","tags":["network","network","compression","compression"]},{"name":"storage.wiredTiger.collectionConfig.blockCompressor","default":"snappy","recommended":"snappy (скорость) или zstd (сильное сжатие)","unit":"algorithm","dynamic":"restart","desc":"Задает алгоритм блочного сжатия, который WiredTiger использует при записи данных коллекций на диск. snappy ориентирован на скорость, zlib и zstd дают более высокую степень сжатия. Применяется только к коллекциям, созданным после настройки, и требует перезапуска.","tags":["storage","storage","compression","compression","wiredtiger"]},{"name":"storage.directoryPerDB","default":"false","recommended":"true, если нужно разнести по дискам","unit":"true/false","dynamic":"restart","desc":"Хранит данные каждой базы данных в отдельном подкаталоге, чтобы разные базы можно было разместить на разных томах. Можно включить только на пустом каталоге данных. Изменение вносится в файл конфигурации и требует перезапуска.","tags":["storage","storage","directory"]},{"name":"setParameter.cursorTimeoutMillis","default":"600000","recommended":"По умолчанию (10 минут), в зависимости от нагрузки","unit":"ms","dynamic":"dynamic","desc":"Задает в миллисекундах, сколько простаивающие курсоры ждут до автоматического закрытия сервером. Не дает брошенным курсорам расходовать ресурсы. Можно изменить сразу во время работы через db.adminCommand({setParameter:1}).","tags":["cursor","timeout","setparameter"]},{"name":"setParameter.transactionLifetimeLimitSeconds","default":"60","recommended":"По умолчанию, если не нужны длинные транзакции","unit":"seconds","dynamic":"dynamic","desc":"Задает в секундах максимальное время, в течение которого многодокументная транзакция может оставаться открытой до автоматического прерывания. Не дает долгим транзакциям удерживать ресурсы и кэш WiredTiger. Можно изменить сразу через setParameter.","tags":["transaction","transaction","setparameter"]},{"name":"setParameter.maxTransactionLockRequestTimeoutMillis","default":"5","recommended":"По умолчанию, в зависимости от конкуренции за блокировки","unit":"ms","dynamic":"dynamic","desc":"Задает в миллисекундах максимальное время ожидания транзакцией нужной блокировки до тайм-аута. Настройка ожидания блокировок не дает транзакциям блокироваться слишком долго. Можно изменить сразу через setParameter.","tags":["transaction","lock","lock","setparameter"]},{"name":"net.tls.mode","default":"disabled","recommended":"requireTLS (в продуктиве)","unit":"mode","dynamic":"restart","desc":"Определяет использование сервером зашифрованных подключений TLS/SSL. requireTLS требует шифрования всех подключений; preferTLS и allowTLS дают промежуточные режимы для переходного периода. Используется вместе с настройкой сертификатов и требует перезапуска.","tags":["security","security","tls","ssl","encryption"]},{"name":"storage.syncPeriodSecs","default":"60","recommended":"60 (не меняйте значение по умолчанию)","unit":"seconds","dynamic":"dynamic","desc":"Задает в секундах, как часто WiredTiger выполняет контрольные точки, т.е. интервал между сбросами данных из памяти на диск. Значение по умолчанию подходит для большинства нагрузок, менять его обычно не рекомендуется. Задается в файле конфигурации; во время работы также меняется без перезапуска через db.adminCommand({ setParameter: 1, syncdelay: <seconds> }).","tags":["storage","storage","checkpoint","sync"]},{"name":"replication.enableMajorityReadConcern","default":"true","recommended":"true (в 5.0+ не меняется)","unit":"true/false","dynamic":"restart","desc":"Позволяет использовать уровень read concern majority; он гарантирует чтение данных, записанных на большинство членов, которые не будут откачены. Начиная с MongoDB 5.0 параметр не меняется и всегда true; в старых версиях (например, в архитектуре PSA) его можно было установить в false. Задается в файле конфигурации и требует перезапуска.","tags":["replication","read concern","replica set"]},{"name":"processManagement.fork","default":"false","recommended":"false с systemd, true для ручного демона","unit":"true/false","dynamic":"restart","desc":"Заставляет процесс mongod выполнить fork и работать в фоне как демон. С systemd или подобным менеджером служб обычно остается false, так как управлением процессом занимается менеджер служб. Задается в файле конфигурации и влияет на поведение при запуске.","tags":["process","process","daemon","fork"]}]},"reference/params-redis.json":{"engineId":"redis","engine":"Redis","order":6,"params":[{"name":"maxmemory","default":"0 (unlimited)","recommended":"~70-75% физического RAM","unit":"bytes","dynamic":"dynamic","desc":"Задает максимальный объем памяти, который Redis может использовать для данных. Значение 0 означает неограниченное использование и может привести к нехватке памяти; задать лимит настоятельно рекомендуется. Можно изменить сразу через CONFIG SET maxmemory.","tags":["memory","memory"]},{"name":"maxmemory-policy","default":"noeviction","recommended":"allkeys-lru для кэша, иначе noeviction","unit":"policy","dynamic":"dynamic","desc":"Политика, определяющая, какие ключи и как Redis вытесняет при достижении лимита maxmemory. noeviction возвращает ошибки записи, а allkeys-lru удаляет давно не использовавшиеся ключи. Можно изменить сразу через CONFIG SET.","tags":["memory","memory","eviction","cache"]},{"name":"maxclients","default":"10000","recommended":"По нагрузке и ulimit ОС","unit":"count","dynamic":"dynamic","desc":"Задает максимальное число принимаемых одновременных клиентских подключений. Лимит файловых дескрипторов операционной системы может ограничивать это значение. Можно изменить сразу через CONFIG SET maxclients.","tags":["connections","connection","clients"]},{"name":"appendonly","default":"no","recommended":"yes, если нужна персистентность","unit":"yes/no","dynamic":"dynamic","desc":"Включает механизм персистентности AOF (Append Only File); при включении каждая операция записи дописывается в файл журнала. Дает меньший риск потери данных, чем снимки RDB. Можно включить сразу через CONFIG SET appendonly yes.","tags":["persistence","persistence","aof"]},{"name":"appendfsync","default":"everysec","recommended":"everysec (сбалансированно)","unit":"policy","dynamic":"dynamic","desc":"Задает, как часто файл AOF синхронизируется на диск (fsync). always синхронизирует при каждой записи — надежнее всего, но медленно; everysec синхронизирует раз в секунду, балансируя производительность и надежность; no оставляет синхронизацию операционной системе. Можно изменить сразу через CONFIG SET.","tags":["persistence","persistence","aof","fsync"]},{"name":"save","default":"3600 1 300 100 60 10000","recommended":"Зависит от нагрузки или отключено (только AOF)","unit":"rule","dynamic":"dynamic","desc":"Задает условия (заданное число изменений за заданное время), при которых снимок RDB сохраняется на диск. Пустое значение полностью отключает снимки RDB. Можно изменить сразу через CONFIG SET save.","tags":["persistence","persistence","rdb","snapshot"]},{"name":"tcp-keepalive","default":"300","recommended":"300 (значение по умолчанию подходит)","unit":"seconds","dynamic":"dynamic","desc":"Задает в секундах интервал, с которым Redis отправляет TCP keepalive ACK клиентским подключениям. Обеспечивает обнаружение и закрытие мертвых подключений и очистку полуоткрытых. Можно изменить сразу через CONFIG SET.","tags":["network","network","tcp","keepalive"]},{"name":"timeout","default":"0","recommended":"0 (никогда не закрывать подключения) или разумное время простоя","unit":"seconds","dynamic":"dynamic","desc":"Задает в секундах максимальное время простоя клиентского подключения; при превышении подключение закрывается. Значение 0 означает, что клиенты могут простаивать бесконечно. Можно изменить сразу через CONFIG SET timeout.","tags":["connections","connection","timeout","idle"]},{"name":"hz","default":"10","recommended":"10 (до 100 на нагруженных системах)","unit":"Hz","dynamic":"dynamic","desc":"Задает, сколько раз в секунду Redis выполняет фоновые задачи (истечение ключей, проверка тайм-аутов клиентов и т.д.). Большие значения дают более точный контроль, но увеличивают использование CPU. Можно изменить сразу через CONFIG SET hz.","tags":["performance","cpu","background"]},{"name":"activedefrag","default":"no","recommended":"yes, если фрагментация памяти высокая","unit":"yes/no","dynamic":"dynamic","desc":"Включает активную дефрагментацию памяти; реорганизует данные во время работы для снижения фрагментации. Полезно на долго работающих экземплярах с высокой фрагментацией. Можно включить сразу через CONFIG SET activedefrag yes.","tags":["memory","memory","defragmentation"]},{"name":"maxmemory-samples","default":"5","recommended":"5 (можно повысить для более точного вытеснения)","unit":"count","dynamic":"dynamic","desc":"Задает число ключей, которые алгоритмы вытеснения LRU и LFU выбирают для выборки при выборе ключа. Большие значения ближе к настоящему поведению LRU/LFU, но немного больше расходуют CPU. Можно изменить сразу через CONFIG SET.","tags":["memory","memory","eviction","lru"]},{"name":"io-threads","default":"1","recommended":"2-4 на многоядерных серверах (в зависимости от числа ядер)","unit":"count","dynamic":"restart","desc":"Задает число потоков I/O для записи в клиентские сокеты и чтения из них. Может повысить пропускную способность на многоядерных системах с интенсивным сетевым трафиком. Этот параметр меняется только в файле конфигурации и требует перезапуска.","tags":["performance","threads","io","cpu"]},{"name":"repl-backlog-size","default":"1mb","recommended":"Больше, исходя из скорости записи и длительности обрыва","unit":"bytes","dynamic":"dynamic","desc":"Задает размер буфера репликации, хранимого на primary для частичной ресинхронизации при обрыве подключения реплики. Достаточно большой буфер позволяет реплике догнать после коротких обрывов без полной синхронизации. Можно изменить сразу через CONFIG SET.","tags":["replication","replication","backlog"]},{"name":"repl-diskless-sync","default":"yes","recommended":"yes (в быстрых сетях)","unit":"yes/no","dynamic":"dynamic","desc":"При полной синхронизации отправляет данные RDB репликам напрямую через сокет без записи на диск. Ускоряет синхронизацию на системах с узким местом в дисковом I/O. Можно изменить сразу через CONFIG SET repl-diskless-sync.","tags":["replication","replication","diskless","sync"]},{"name":"requirepass","default":"(empty)","recommended":"Задайте надежный пароль","unit":"password","dynamic":"dynamic","desc":"Задает пароль, с которым клиенты должны пройти аутентификацию через AUTH до выполнения команд. Чтобы предотвратить несанкционированный доступ к Redis, нужно задать надежный пароль. Можно изменить сразу через CONFIG SET requirepass.","tags":["security","security","authentication","password"]},{"name":"cluster-enabled","default":"no","recommended":"yes, если нужно горизонтальное масштабирование","unit":"yes/no","dynamic":"restart","desc":"Включает режим Redis Cluster; при включении данные распределяются по нескольким узлам через hash slots. Используется для горизонтального масштабирования и автоматического секционирования данных. Этот параметр меняется только в файле конфигурации и требует перезапуска.","tags":["cluster","cluster","scaling","scaling"]},{"name":"cluster-node-timeout","default":"15000","recommended":"15000 или значение, подобранное под сетевую задержку","unit":"ms","dynamic":"dynamic","desc":"В режиме кластера задает в миллисекундах максимальное время без ответа, после которого узел считается недоступным (fail). Напрямую влияет на поведение failover и стабильность кластера. Можно изменить сразу через CONFIG SET.","tags":["cluster","cluster","timeout","failover"]},{"name":"aof-use-rdb-preamble","default":"yes","recommended":"yes (быстрая перезагрузка)","unit":"yes/no","dynamic":"dynamic","desc":"Включает гибридный формат персистентности, при котором перезапись AOF записывает в начало файла компактный снимок RDB, а за ним инкрементальные команды AOF. Сокращает время загрузки AOF при перезапуске. Можно изменить сразу через 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% физического RAM, не более ~30-31GB (Xms=Xmx)","unit":"GB","dynamic":"restart","desc":"Задает начальный (-Xms) и максимальный (-Xmx) размер heap JVM; оба следует задавать одинаковыми. Рекомендуется не превышать половину общего RAM и предел ~32GB, после которого перестают действовать сжатые указатели объектов. Задается в jvm.options и требует перезапуска узла.","tags":["memory","memory","jvm","heap"]},{"name":"indices.memory.index_buffer_size","default":"10%","recommended":"10% (можно увеличить при высокой нагрузке индексирования)","unit":"% / bytes","dynamic":"restart","desc":"Задает долю heap, выделяемую буферу памяти, в котором хранятся новые документы при индексировании. Увеличение буфера может повысить пропускную способность индексирования при нагрузках с преобладанием записи. Задается для каждого узла в elasticsearch.yml и требует перезапуска.","tags":["memory","memory","indexing","buffer"]},{"name":"thread_pool.write.queue_size","default":"10000","recommended":"10000 (осторожно увеличивайте при отказах очереди)","unit":"count","dynamic":"restart","desc":"Задает длину очереди пула потоков для операций записи index, delete и bulk. Когда очередь заполнена, запросы отклоняются. Слишком большое значение может вызвать нехватку памяти. Задается в elasticsearch.yml и требует перезапуска.","tags":["thread pool","write","queue","queue"]},{"name":"thread_pool.search.queue_size","default":"1000","recommended":"1000 (осторожно увеличивайте при отказах поиска)","unit":"count","dynamic":"restart","desc":"Задает длину очереди пула потоков для операций поиска. Когда одновременные поиски превышают емкость пула, запросы ждут в этой очереди и отклоняются при ее заполнении. Задается в elasticsearch.yml и требует перезапуска.","tags":["thread pool","search","search","queue"]},{"name":"indices.fielddata.cache.size","default":"unbounded","recommended":"Лимит порядка 20-40% (для защиты памяти)","unit":"% / bytes","dynamic":"restart","desc":"Задает максимальный размер кэша fielddata, используемого для сортировки и агрегаций по текстовым полям. Поскольку по умолчанию он не ограничен, рекомендуется задать лимит, чтобы не исчерпать память. Задается в elasticsearch.yml и требует перезапуска.","tags":["memory","memory","fielddata","cache"]},{"name":"index.number_of_shards","default":"1","recommended":"По объему данных; цель — 10-50GB на шард","unit":"count","dynamic":"restart","desc":"Задает число первичных шардов, на которые делится индекс. Это статический параметр индекса (не перезапуск, а задается только при создании индекса): на существующем индексе напрямую не меняется; меняйте, создавая новый индекс через новый индекс + _reindex или через API _split (умножение) / _shrink (уменьшение до делителя). Слишком много шардов увеличивает накладные расходы кластера, слишком мало ограничивает масштабирование.","tags":["index","index","shard","scaling"]},{"name":"index.number_of_replicas","default":"1","recommended":"В продуктиве не менее 1 (для высокой доступности)","unit":"count","dynamic":"dynamic","desc":"Задает число шардов-реплик для каждого первичного шарда. Реплики обеспечивают и высокую доступность, и производительность чтения. Параметр можно изменить мгновенно на работающем индексе через API _settings.","tags":["index","index","replica","availability"]},{"name":"index.refresh_interval","default":"1s","recommended":"30s или -1 (временно) при интенсивном индексировании","unit":"duration","dynamic":"dynamic","desc":"Задает, как часто выполняется refresh, делающий новые проиндексированные документы видимыми для поиска. Увеличение значения или установка -1 при массовой загрузке значительно повышает пропускную способность индексирования. Можно изменить мгновенно через API _settings.","tags":["index","index","refresh","indexing"]},{"name":"indices.breaker.total.limit","default":"95%","recommended":"95% (по умолчанию; меняйте осторожно)","unit":"%","dynamic":"dynamic","desc":"Не дает узлу упасть с OutOfMemory, задавая максимальную долю heap, которую могут использовать все circuit breaker вместе. Можно изменить мгновенно для всего кластера через API _cluster/settings.","tags":["memory","memory","circuit breaker","stability"]},{"name":"cluster.routing.allocation.disk.watermark.low","default":"85%","recommended":"85% (значение по умолчанию подходит)","unit":"% / bytes","dynamic":"dynamic","desc":"Когда использование диска узла превышает этот порог, Elasticsearch перестает размещать на нем новые шарды (существующие остаются). Это первый уровень предупреждения против заполнения диска. Можно изменить мгновенно через API _cluster/settings.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.high","default":"90%","recommended":"90% (значение по умолчанию подходит)","unit":"% / bytes","dynamic":"dynamic","desc":"Когда использование диска узла превышает этот порог, Elasticsearch начинает перемещать шарды с этого узла на другие. Разгружает узлы, диски которых заполняются. Можно изменить мгновенно через API _cluster/settings.","tags":["disk","allocation","watermark","storage"]},{"name":"cluster.routing.allocation.disk.watermark.flood_stage","default":"95%","recommended":"95% (значение по умолчанию подходит)","unit":"% / bytes","dynamic":"dynamic","desc":"Когда использование диска превышает этот критический порог, индексы, у которых есть шард на затронутом узле, переводятся в режим только для чтения (read-only-allow-delete), и запись блокируется. Предотвращает повреждение данных до полного заполнения диска; в 7.4+ блокировка снимается автоматически, когда использование диска падает ниже high watermark. В 8.x на больших дисках решающим является max_headroom (по умолчанию 100GB свободно), а не процент. Можно изменить мгновенно через API _cluster/settings.","tags":["disk","allocation","watermark","read-only"]},{"name":"index.translog.durability","default":"request","recommended":"request (надежность) или async (производительность)","unit":"mode","dynamic":"dynamic","desc":"Задает, когда журнал транзакций (translog) синхронизируется (fsync) на диск. request синхронизирует после каждого запроса и минимизирует потерю данных; async синхронизирует периодически и повышает производительность, но несет риск потери данных при сбое. Можно изменить мгновенно через API _settings.","tags":["translog","durability","durability","indexing"]},{"name":"indices.recovery.max_bytes_per_sec","default":"40mb","recommended":"Можно увеличить исходя из пропускной способности сети и диска","unit":"bytes/s","dynamic":"dynamic","desc":"Задает максимальную скорость передачи данных на узел при восстановлении шардов. Увеличение ускоряет восстановление, но может отнимать сетевые и дисковые ресурсы у обычной нагрузки. Можно изменить мгновенно через API _cluster/settings.","tags":["recovery","recovery","throttle","network"]},{"name":"action.destructive_requires_name","default":"true","recommended":"true (блокирует разрушительные операции по шаблону)","unit":"true/false","dynamic":"dynamic","desc":"Блокирует разрушительные операции, такие как удаление индексов по шаблону (*) или _all, и требует явно указывать имя индекса. Важный параметр безопасности против случайного удаления всех индексов. Можно изменить мгновенно через API _cluster/settings.","tags":["security","security","destructive","safety"]},{"name":"discovery.seed_hosts","default":"(empty / 127.0.0.1)","recommended":"Адреса master-eligible узлов кластера","unit":"list","dynamic":"restart","desc":"Задает список адресов seed-хостов (master-eligible узлов), к которым обращается узел для присоединения к кластеру. Необходим для обнаружения кластера и для того, чтобы узлы находили друг друга. Задается в elasticsearch.yml и требует перезапуска.","tags":["discovery","cluster","cluster","network"]}]},"reference/waits-oracle.json":{"engineId":"oracle","engine":"Oracle","order":1,"waits":[{"name":"log file sync","kind":"wait","meaning":"Сессия выполнила COMMIT (или ROLLBACK) и ждет, пока процесс LGWR запишет redo-записи в оперативные redo-журналы и подтвердит.","causes":"Медленный I/O redo log, очень частые мелкие commit, перегруженный процесс LGWR, задержка LGWR из-за нехватки CPU, redo-журналы на медленных дисках.","action":"Снизьте частоту commit (пакетные commit), перенесите redo-журналы на быстрые диски с низкой задержкой (SSD), уберите лишние commit, рассматривайте COMMIT NOWAIT/BATCH только для работ, где потеря допустима (ВНИМАНИЕ: это снижает надежность; транзакции, выглядящие зафиксированными, могут быть потеряны при сбое), и измеряйте задержку I/O log writer (log file parallel write).","tags":["commit","redo","io","lgwr"]},{"name":"db file sequential read","kind":"wait","meaning":"Сессия выполняет одноблочное физическое чтение; ждет, пока блок будет считан с диска в буферный кэш, обычно при доступе по индексу или чтении блока таблицы по ROWID.","causes":"Индексные сканирования, неэффективные планы доступа, маленький буферный кэш, медленная дисковая подсистема, блоки не находятся в кэше из-за большого числа физических чтений.","action":"Настройте SQL, чтобы сократить лишние чтения, добавьте подходящие индексы, увеличьте размер буферного кэша, улучшите подсистему I/O и найдите самые читающие запросы через AWR/ASH.","tags":["io","index","single-block","read"]},{"name":"db file scattered read","kind":"wait","meaning":"Сессия выполняет многоблочное физическое чтение; ждет чтения нескольких блоков с диска, обычно при полном сканировании таблицы или fast full index scan.","causes":"Полные сканирования таблиц, отсутствующие или неиспользуемые индексы, неэффективные планы запросов, сканирования больших таблиц, неверная статистика оптимизатора.","action":"Предотвратите лишние полные сканирования, добавив индексы, проверьте планы запросов, обновите статистику, изучите настройку db_file_multiblock_read_count и рассмотрите распараллеливание действительно нужных полных сканирований.","tags":["io","full-scan","multi-block","read"]},{"name":"direct path read","kind":"wait","meaning":"Сессия читает блоки напрямую в PGA, минуя буферный кэш; обычно наблюдается при параллельных запросах, сканированиях больших таблиц или чтении LOB.","causes":"Параллельные полные сканирования таблиц, решение о последовательном direct path read (adaptive direct read), сканирования больших таблиц, чтение временного табличного пространства (temp), доступ к LOB.","action":"Сократите потребность в больших сканированиях настройкой запросов, оцените размер PGA, оптимизируйте операции сортировки/хеширования при высоком использовании temp и увеличьте пропускную способность I/O.","tags":["io","direct-path","parallel","read"]},{"name":"direct path write","kind":"wait","meaning":"Сессия записывает блоки напрямую на диск, минуя буферный кэш; обычно наблюдается при direct-path вставках, сбросах сортировки на диск или записи LOB.","causes":"Операции direct-path INSERT (APPEND), сбросы сортировки/хеширования во временное табличное пространство, параллельный DML, запись LOB, медленные диски temp или данных.","action":"Увеличьте PGA для операций сортировки/хеширования, чтобы сократить сбросы в temp, разместите временное табличное пространство на быстрых дисках, проверьте лишние direct-path загрузки и улучшите производительность I/O.","tags":["io","direct-path","temp","write"]},{"name":"buffer busy waits","kind":"wait","meaning":"Сессия хочет обратиться к блоку в буферном кэше, но блок используется другой сессией в несовместимом режиме, поэтому она ждет его освобождения.","causes":"Интенсивный конкурентный доступ к одному блоку (hot block), недостаточно freelists/ASSM, конкуренция за правый блок последовательного индекса (right-hand index growth), сегменты с высокой интенсивностью DML.","action":"Распределите горячие блоки (hash-секционирование, reverse key index), используйте ASSM, увеличьте freelists/freelist groups, улучшите кэширование последовательностей и распределение на таблицах с последовательными ключами.","tags":["concurrency","buffer","hot-block","contention"]},{"name":"read by other session","kind":"wait","meaning":"Другая сессия уже читает с диска блок, нужный этой сессии; эта сессия ждет завершения чтения того же блока.","causes":"Одновременные запросы физического чтения тех же горячих блоков, маленький буферный кэш, медленный I/O, множество сессий, одновременно обращающихся к популярным блокам индексов или таблиц.","action":"Увеличьте буферный кэш, чтобы сократить физические чтения, выявите горячие сегменты и улучшите планы доступа к ним, снизьте задержку I/O и устраните лишние полные сканирования.","tags":["io","concurrency","read","hot-block"]},{"name":"latch: cache buffers chains","kind":"wait","meaning":"Сессия ждет получения latch, защищающей хеш-цепочку буферного кэша; при поиске блоков в буферном кэше возникает сериализация.","causes":"Неэффективный SQL с избыточным логическим I/O, интенсивный конкурентный доступ к горячим блокам, повторный доступ к одним и тем же блокам через nested loops, очень популярные корневые/ветвевые блоки индексов.","action":"Настройте SQL, чтобы сократить логические чтения, устраните лишний доступ через nested loop, распределите горячие блоки, найдите и исправьте запросы с избыточными buffer gets через AWR/ASH.","tags":["latch","concurrency","logical-io","buffer"]},{"name":"cursor: pin S wait on X","kind":"wait","meaning":"Сессия хочет закрепить курсор мьютексом в разделяемом (S) режиме, но ждет, так как другая сессия удерживает тот же курсор в монопольном (X) режиме.","causes":"Высокая частота hard/soft parse, популярный SQL, разбираемый очень часто, запросы с литералами без bind-переменных, разрастание дочерних курсоров, множество сессий, одновременно разбирающих один и тот же SQL.","action":"Снизьте нагрузку разбора с помощью bind-переменных, кэшируйте курсоры в приложении (session_cached_cursors), сократите лишние повторные разборы и исправьте SQL с высоким version count.","tags":["mutex","parse","cursor","concurrency"]},{"name":"library cache lock","kind":"wait","meaning":"Сессия ждет получения блокировки на дескрипторе объекта в библиотечном кэше (пакет, представление, определение таблицы); обычно наблюдается при конкурентном доступе к определению объекта или его изменении.","causes":"DDL над используемыми объектами, постоянная перекомпиляция пакетов/представлений, невалидные объекты, интенсивные одновременные подключения и разбор, изменения привилегий (grant).","action":"Выполняйте DDL вне пиковых часов, предотвращайте лишние компиляции, исправляйте невалидные объекты и сокращайте избыточные циклы connect/parse на стороне приложения.","tags":["library-cache","concurrency","ddl","parse"]},{"name":"library cache: mutex X","kind":"wait","meaning":"Сессия ждет получения мьютекса для защиты объекта в библиотечном кэше в монопольном (X) режиме; обычно наблюдается при интенсивном разборе и конкуренции за дескрипторы объектов.","causes":"Избыточный разбор (hard/soft), большое разнообразие SQL с литералами без bind, большое число дочерних курсоров, частая инвалидация объектов, множество сессий, одновременно обращающихся к одному объекту.","action":"Используйте bind-переменные и кэширование курсоров для сокращения разбора, стандартизируйте литералы SQL, исправьте проблемы высокого version count и улучшите поведение подключений приложения.","tags":["mutex","library-cache","parse","concurrency"]},{"name":"enq: TX - row lock contention","kind":"wait","meaning":"Сессия хочет изменить строку, заблокированную другой транзакцией, и ждет, пока та выполнит commit/rollback и освободит блокировку строки.","causes":"Одновременные UPDATE/DELETE одних и тех же строк, долгие транзакции без commit, плохо спроектированный процесс в приложении, косвенные блокировки строк через уникальные/внешние ключи, зависшие сессии.","action":"Делайте транзакции короткими и своевременно выполняйте commit, перепроектируйте конфликтный шаблон доступа, при необходимости обеспечьте упорядоченный доступ через SELECT FOR UPDATE, найдите и устраните блокирующую сессию через ASH.","tags":["lock","transaction","row-lock","contention"]},{"name":"enq: TX - allocate ITL entry","kind":"wait","meaning":"Сессии для работы с блоком нужен слот ITL (Interested Transaction List), но она вынуждена ждать, так как свободных слотов ITL в блоке не осталось.","causes":"Низкое значение INITRANS, маленький размер блока, много одновременных транзакций в одном блоке, плотно заполненные блоки с интенсивным DML.","action":"Увеличьте INITRANS для таблицы/индекса, повысьте PCTFREE, чтобы размещать меньше строк в блоке, распределите горячие блоки и при необходимости перестройте объект.","tags":["lock","transaction","itl","block"]},{"name":"gc buffer busy acquire","kind":"wait","meaning":"В среде RAC сессия хочет получить блок через глобальный кэш, но ждет, так как другая сессия того же экземпляра уже начала глобальный запрос того же блока.","causes":"Совместное использование горячих блоков экземплярами RAC, недостаточная производительность interconnect, конкуренция за правый блок последовательного индекса, плохо распределенная нагрузка, интенсивный трафик глобального кэша.","action":"Разумно распределите нагрузку по экземплярам (application affinity/services), распределите горячие сегменты (reverse key/partition), снизьте задержку interconnect и анализируйте конкуренцию gc через раздел RAC в AWR и представления GV$.","tags":["rac","global-cache","concurrency","interconnect"]},{"name":"gc cr block busy","kind":"wait","meaning":"В среде RAC сессия запросила блок для согласованного чтения (CR) у другого экземпляра и ждет, пока тот подготовит блок и отправит его через interconnect.","causes":"Совместное использование горячих блоков экземплярами, медленный или перегруженный interconnect, интенсивный трафик redo/commit, плохо распределенная нагрузка, избыточные CR-запросы глобального кэша.","action":"Улучшите affinity нагрузки, оптимизируйте пропускную способность и задержку interconnect, сократите горячие блоки, пересмотрите шаблоны commit и DML и настройте RAC.","tags":["rac","global-cache","cr","interconnect"]},{"name":"log file switch (checkpoint incomplete)","kind":"wait","meaning":"Сессия хочет переключиться на новый redo-журнал, но контрольная точка для следующего файла журнала еще не завершена, поэтому LGWR не может повторно использовать этот журнал и вся генерация redo останавливается.","causes":"Слишком маленькие или слишком немногочисленные redo-журналы, медленный I/O DBWR/контрольных точек, высокая скорость генерации redo, недостаточное число групп журналов.","action":"Увеличьте redo-журналы и добавьте группы, улучшите производительность I/O DBWR/контрольных точек, оптимизируйте приложение для снижения генерации redo и отслеживайте частоту переключения журналов.","tags":["redo","checkpoint","log-switch","io"]},{"name":"free buffer waits","kind":"wait","meaning":"Сессия ищет чистый/свободный буфер в буферном кэше, чтобы прочитать новый блок, но ждет, так как DBWR не успевает записывать грязные буферы на диск.","causes":"Медленный I/O записи DBWR, маленький буферный кэш, быстрое появление грязных буферов из-за интенсивного DML, недостаточная скорость контрольных точек, перегруженная дисковая подсистема.","action":"Ускорьте I/O записи файлов данных, увеличьте размер буферного кэша, оцените число процессов DBWR (db_writer_processes), пересмотрите интенсивность DML и настройки контрольных точек.","tags":["buffer","dbwr","io","checkpoint"]},{"name":"Buffer Cache Hit Ratio","kind":"metric","meaning":"Доля запрошенных блоков, обслуженных из буферного кэша без обращения к диску; показывает, какой процент логических чтений не потребовал физического чтения.","causes":"Низкое значение обычно вызвано маленьким буферным кэшем, неэффективными полными сканированиями, холодным кэшем или большим числом физических чтений. Само по себе может вводить в заблуждение; высокое значение тоже может скрывать плохой SQL.","action":"Не ставьте целью само соотношение; оценивайте его вместе с событиями ожидания и трендами физических чтений. Настройте неэффективный SQL, сократите лишние логические чтения и подберите размер кэша под нагрузку.","tags":["metric","buffer-cache","ratio","memory"]},{"name":"Average Active Sessions","kind":"metric","meaning":"Среднее число одновременно активных сессий (выполняющихся на CPU или ожидающих события) за интервал; обобщает общую нагрузку и интенсивность узких мест базы данных.","causes":"Высокий AAS может быть вызван насыщением CPU, ожиданиями I/O, конкуренцией за блокировки/latch или ростом конкурентности. Значения, приближающиеся к числу ядер CPU или превышающие его, указывают на узкое место в ресурсах.","action":"Сравните AAS с числом ядер CPU, найдите преобладающий класс ожиданий (CPU/IO/concurrency) через ASH, настройте SQL и сессии с наибольшей нагрузкой и устраните проблему ресурсов или конкурентности.","tags":["metric","load","ash","sessions"]}]},"reference/waits-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"waits":[{"name":"LWLock:WALWrite","kind":"wait","meaning":"Сессия ждет легковесную блокировку, необходимую для записи буферов WAL на диск; обычно вызвано сериализацией записи WAL при commit.","causes":"Медленный дисковый I/O WAL, очень частые/мелкие commit, высокая нагрузка записи, недостаточная пропускная способность записи WAL, множество backend-процессов, одновременно пишущих WAL.","action":"Перенесите WAL на быстрый диск с низкой задержкой, группируйте commit (оцените synchronous_commit, commit_delay), увеличьте wal_buffers, сократите лишние операции записи.","tags":["wal","lwlock","commit","io"]},{"name":"LWLock:BufferContent","kind":"wait","meaning":"Сессия ждет легковесную блокировку буфера в shared buffer pool, защищающую содержимое страницы; указывает на конкуренцию из-за одновременного доступа к странице.","causes":"Интенсивные одновременные чтения/записи одной страницы (hot page), конкуренция за правый край последовательных индексов, маленькие таблицы с интенсивными обновлениями, высокая конкурентность.","action":"Распределите горячие страницы (настройка fillfactor, секционирование), снизьте конкуренцию за последовательные ключи, перестройте интенсивно обновляемые строки, пересмотрите шаблон конкурентного доступа.","tags":["lwlock","buffer","concurrency","contention"]},{"name":"Lock:transactionid","kind":"wait","meaning":"Сессия ждет завершения (commit/rollback) другой транзакции, заблокировавшей строку, которую она хочет изменить; ожидание блокировки идет по ID целевой транзакции.","causes":"Одновременные UPDATE/DELETE одних и тех же строк, долгие транзакции без commit, сессии idle in transaction, плохо спроектированный конфликтный процесс.","action":"Делайте транзакции короткими и своевременно выполняйте commit, ограничьте сессии idle in transaction (idle_in_transaction_session_timeout), перепроектируйте конфликтный шаблон доступа, найдите блокирующий запрос через pg_stat_activity.","tags":["lock","transaction","row-lock","contention"]},{"name":"Lock:tuple","kind":"wait","meaning":"Сессия ждет получения блокировки определенного кортежа (строки); наблюдается, когда несколько сессий выстраиваются в очередь на обновление/блокировку одной строки.","causes":"Одновременные SELECT FOR UPDATE/UPDATE одной строки, конкуренция за горячую строку (например, строки-счетчики), долго удерживаемые блокировки строк, высокая конкурентность.","action":"Распределите или выстройте в очередь обновления горячих строк, сократите длительность транзакций, перепроектируйте конфликтные шаблоны вроде счетчиков (например, пакетные обновления), рассмотрите FOR UPDATE SKIP LOCKED.","tags":["lock","tuple","row-lock","contention"]},{"name":"Lock:relation","kind":"wait","meaning":"Сессия ждет получения блокировки уровня таблицы на таблицу/индекс (relation); обычно наблюдается при конкуренции несовместимых режимов блокировок DDL и DML.","causes":"DDL вроде ALTER/DROP/TRUNCATE во время выполнения запросов, VACUUM FULL, требующий жесткой блокировки, одновременный доступ к одной таблице в несовместимых режимах, долгие запросы.","action":"Выполняйте DDL в спокойные периоды с коротким lock_timeout, используйте регулярный autovacuum вместо VACUUM FULL, отслеживайте долгие запросы, пересмотрите операции, сериализующие доступ к таблице.","tags":["lock","relation","ddl","table-lock"]},{"name":"IO:DataFileRead","kind":"wait","meaning":"Сессия ждет чтения страницы файла данных с диска в shared buffers; это ожидание физического чтения, возникающее, когда запрошенной страницы нет в кэше.","causes":"Маленький shared_buffers, неэффективные планы запросов и большие сканирования, холодный кэш, медленная дисковая подсистема, недостаточное использование индексов.","action":"Настройте shared_buffers и page cache ОС под нагрузку, исправьте неэффективные запросы и отсутствующие индексы, улучшите производительность I/O, правильно задайте effective_cache_size.","tags":["io","read","buffer","disk"]},{"name":"IO:WALSync","kind":"wait","meaning":"Сессия ждет надежной записи WAL на диск (fsync/синхронизация); для надежности commit она ждет физического сброса WAL.","causes":"Высокая задержка fsync хранилища, очень частые commit, низкопроизводительный диск WAL, диск без write-back кэша, высокая нагрузка записи.","action":"Перенесите диск WAL на быстрое хранилище с низкой задержкой fsync, группируйте commit (commit_delay/commit_siblings), оцените synchronous_commit относительно бизнес-требований, улучшите уровень хранения.","tags":["wal","io","fsync","commit"]},{"name":"IO:DataFileWrite","kind":"wait","meaning":"Сессия ждет записи грязной страницы данных на диск; обычно наблюдается, когда backend сам выполняет запись, чтобы найти чистую страницу в буферном пуле.","causes":"Медленный I/O записи на диск данных, перегрузка контрольных точек, недостаточный background writer, маленький буферный пул, backend сам выполняет запись из-за высокой нагрузки записи.","action":"Сгладьте контрольные точки (checkpoint_completion_target, max_wal_size), настройте background writer (bgwriter_lru_maxpages), улучшите производительность записи на диск, пересмотрите shared_buffers.","tags":["io","write","checkpoint","bgwriter"]},{"name":"Client:ClientRead","kind":"wait","meaning":"Backend ждет следующую команду/данные от клиента; наблюдается, когда сервер простаивает в ожидании отправки данных клиентом.","causes":"Медленная сеть, сессии idle in transaction, клиентское приложение медленно отправляет/получает данные, большие наборы результатов потребляются медленно, проблемы пула подключений.","action":"Изучите задержки на стороне приложения и сети, ограничьте сессии idle in transaction, потребляйте результаты эффективно (например, cursor/batch), улучшите пул подключений. Обычно это ожидание не является узким местом базы данных.","tags":["client","network","idle","application"]},{"name":"IPC:ProcArrayGroupUpdate","kind":"wait","meaning":"Сессия ждет, пока backend — лидер группы отметит завершение ее транзакции в ProcArray в составе группы; это сериализация обновлений ProcArray при commit.","causes":"Очень высокая частота commit/транзакций, избыток одновременных коротких транзакций, большое число подключений, интенсивный доступ к snapshot/ProcArray.","action":"Управляйте частотой транзакций и числом одновременных подключений (пул подключений), сократите лишние короткие транзакции, ограничьте число подключений при очень высокой конкурентности, сбалансируйте нагрузку.","tags":["ipc","commit","procarray","concurrency"]},{"name":"LWLock:LockManager","kind":"wait","meaning":"Сессия ждет легковесную блокировку менеджера блокировок, защищающую таблицу тяжеловесных блокировок; вызвано сериализацией операций получения/освобождения блокировок.","causes":"Запросы, затрагивающие много таблиц/секций (превышение лимита fast-path блокировок), высокая конкурентность, избыточно секционированные таблицы, нагрузка, получающая/освобождающая много блокировок.","action":"Сократите число таблиц/секций, затрагиваемых одним запросом (partition pruning), пересмотрите избыточное секционирование, управляйте конкурентностью через пул подключений, упростите операции с лишними блокировками.","tags":["lwlock","lock-manager","concurrency","partitioning"]},{"name":"Timeout:VacuumDelay","kind":"wait","meaning":"Процесс vacuum/autovacuum намеренно приостановлен из-за стоимостной задержки vacuum; преднамеренное ожидание для ограничения нагрузки I/O.","causes":"Троттлинг из-за настроек vacuum_cost_delay/autovacuum_vacuum_cost_delay, низкий cost_limit, vacuum обрабатывает много таблиц, неагрессивные настройки autovacuum.","action":"Если vacuum слишком медленный и накапливаются bloat/мертвые кортежи, увеличьте cost_limit и снизьте cost_delay, настройте число рабочих процессов autovacuum и пороги, сбалансируйте с емкостью I/O.","tags":["vacuum","timeout","throttle","maintenance"]},{"name":"коэффициент попаданий в кэш","kind":"metric","meaning":"Доля запросов блоков, обслуженных из shared buffers без обращения к диску; вычисляется из blks_hit и blks_read в pg_stat_database.","causes":"Низкое значение обычно указывает на маленький shared_buffers, неэффективные большие сканирования, холодный кэш или нехватку памяти. Поскольку чтения обслуживает и page cache ОС, само соотношение может вводить в заблуждение.","action":"Интерпретируйте соотношение вместе с событиями ожидания и трендами I/O, настройте shared_buffers и effective_cache_size, исправьте неэффективные запросы; если значение стабильно низкое, изучите память и планы запросов.","tags":["metric","cache","ratio","memory"]},{"name":"отставание репликации","kind":"metric","meaning":"Насколько standby/реплика отстает в применении WAL, созданного primary; измеряется в байтах (разница LSN) или секундах задержки.","causes":"Медленная сеть, высокая нагрузка запросов на standby (особенно конфликты replay), медленный дисковый I/O standby, высокая скорость записи на primary, долгие читающие запросы, задерживающие восстановление.","action":"Отслеживайте отставание write/flush/replay через pg_stat_replication, улучшите производительность сети и I/O standby, управляйте конфликтующими долгими запросами (hot_standby_feedback/max_standby_streaming_delay), пересмотрите необходимость синхронной репликации.","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":"Поток ждет мьютекс, защищающий список LRU буферного пула InnoDB; указывает на сериализацию при чтении/вытеснении страниц. В 8.0 старый единый buf_pool_mutex был разделен; другие связанные инструменты — buf_pool_free_list_mutex, buf_pool_flush_state_mutex и buf_pool_zip_mutex.","causes":"Интенсивный доступ к буферному пулу при высокой конкурентности, единственный экземпляр буферного пула, частые чтения/вытеснения страниц, буферный пул недостаточного размера.","action":"Разделите мьютекс, увеличив innodb_buffer_pool_instances, подберите innodb_buffer_pool_size под рабочий набор, настройте запросы для сокращения физических чтений и управляйте конкурентностью.","tags":["innodb","mutex","buffer-pool","concurrency"]},{"name":"wait/io/file/innodb/innodb_data_file","kind":"wait","meaning":"Поток ждет завершения физического файлового I/O (чтение/запись) с файлами данных InnoDB (ibd/ibdata).","causes":"Медленная дисковая подсистема, много физических чтений из-за маленького буферного пула, сканирования больших таблиц, давление checkpoint/flush, недостаточная емкость I/O.","action":"Увеличьте буферный пул, исправьте неэффективные запросы и отсутствующие индексы, перейдите на быстрое хранилище (SSD/NVMe), задайте innodb_io_capacity по возможностям хранилища и пересмотрите поведение flush.","tags":["innodb","io","datafile","disk"]},{"name":"wait/lock/table/sql/handler","kind":"wait","meaning":"Поток ждет получения блокировки handler уровня таблицы (table lock) в слое SQL; наблюдается при одновременном доступе к таблице в несовместимых режимах.","causes":"Операции, требующие блокировки таблицы, движки хранения с блокировкой уровня таблицы, такие как MyISAM, операторы LOCK TABLES, конкуренция DDL и DML, долгие запросы.","action":"Используйте InnoDB с блокировкой уровня строк, сократите использование LOCK TABLES, выполняйте DDL при низкой активности, оптимизируйте долгие запросы и пересмотрите шаблоны, сериализующие доступ к таблице.","tags":["lock","table-lock","sql-layer","contention"]},{"name":"wait/synch/mutex/sql/LOCK_open","kind":"wait","meaning":"Поток ждет мьютекс LOCK_open, защищающий определения открытых таблиц/словарь объектов; это сериализация операций открытия/закрытия таблиц.","causes":"Постоянный доступ ко многим разным таблицам, маленькие table_open_cache/table_definition_cache, частое открытие и закрытие таблиц, высокая конкурентность, слишком много таблиц/секций.","action":"Увеличьте table_open_cache и table_open_cache_instances, настройте table_definition_cache, пересмотрите избыточное число таблиц/секций и сократите излишне частое открытие таблиц.","tags":["mutex","table-cache","sql-layer","concurrency"]},{"name":"ожидания блокировок строк","kind":"wait","meaning":"Транзакция ждет освобождения блокировки строки InnoDB, удерживаемой другой транзакцией; указывает на конкуренцию блокировок уровня строк.","causes":"Одновременные UPDATE/DELETE одних и тех же строк, долгие транзакции без commit, широкие диапазоны блокировок (gap locks) из-за отсутствия индексов, конкуренция за горячие строки, циклы взаимоблокировок.","action":"Делайте транзакции короткими и своевременно выполняйте commit, сузьте диапазон блокируемых строк подходящим индексом, перепроектируйте конфликтные шаблоны, отслеживайте взаимоблокировки (SHOW ENGINE INNODB STATUS) и пересмотрите уровень изоляции.","tags":["innodb","row-lock","transaction","contention"]},{"name":"ожидания журнала","kind":"wait","meaning":"Поток ждет освобождения места в буфере redo log InnoDB или записи redo log на диск; обычно наблюдается при давлении flush буфера журнала/контрольных точек.","causes":"Маленький redo log (8.0.30+ innodb_redo_log_capacity; до этого innodb_log_file_size × innodb_log_files_in_group), медленный диск журнала, высокая скорость записи, частые flush (innodb_flush_log_at_trx_commit=1 при частых commit), недостаточный буфер журнала.","action":"Увеличьте емкость redo (8.0.30+ innodb_redo_log_capacity, динамически; до этого innodb_log_file_size, требует перезапуска) и innodb_log_buffer_size, перенесите redo log на быстрый диск, пересмотрите шаблон commit и оцените настройку flush относительно требований надежности.","tags":["innodb","redo","log","io"]},{"name":"wait/io/file/innodb/innodb_log_file","kind":"wait","meaning":"Поток ждет завершения физического I/O (запись и fsync) с файлами redo log InnoDB; для надежности commit он ждет, пока redo достигнет диска.","causes":"Высокая задержка fsync хранилища, очень частые commit при innodb_flush_log_at_trx_commit=1, низкопроизводительный диск журнала, высокая нагрузка записи.","action":"Перенесите диск redo log на быстрое хранилище с низкой задержкой, по возможности группируйте commit, оцените innodb_flush_log_at_trx_commit относительно бизнес-требований и улучшите уровень хранения.","tags":["innodb","redo","fsync","commit"]},{"name":"wait/synch/mutex/innodb/log_writer_mutex","kind":"wait","meaning":"Поток ждет мьютекс, защищающий структуры потока записи redo log InnoDB. Подсистема redo была перепроектирована в 8.0; вместо старого единого log_sys_mutex есть отдельные инструменты, такие как log_writer_mutex, log_flusher_mutex и log_checkpointer_mutex.","causes":"Очень высокая частота записи/commit, интенсивная генерация redo, конкуренция за потоки log writer/flusher, медленный диск журнала, маленький буфер журнала.","action":"Сбалансируйте нагрузку записи и частоту commit, рассмотрите пакетные операции, снижающие генерацию redo, настройте буфер журнала и используйте улучшения масштабируемости redo log вашей версии MySQL.","tags":["innodb","mutex","redo","concurrency"]},{"name":"Innodb_buffer_pool_wait_free","kind":"metric","meaning":"Сколько раз поток не находил свободную страницу в буферном пуле и был вынужден ждать сброса грязных страниц, чтобы получить свободную; постоянный рост указывает на давление на буферный пул.","causes":"Маленький буферный пул, медленный I/O flush/записи, низкий innodb_io_capacity, быстрое появление грязных страниц при высокой нагрузке записи, отставание контрольных точек.","action":"Увеличьте innodb_buffer_pool_size, задайте innodb_io_capacity/io_capacity_max по возможностям хранилища, улучшите производительность I/O записи и пересмотрите долю грязных страниц (innodb_max_dirty_pages_pct).","tags":["metric","innodb","buffer-pool","flush"]},{"name":"Threads_running","kind":"metric","meaning":"Число потоков, которые не простаивают и в данный момент активно выполняют запросы; показывает мгновенное одновременное выполнение на сервере и вероятную интенсивность конкуренции.","causes":"Внезапный всплеск запросов, накопление медленных запросов, остановка потоков из-за конкуренции за блокировки/мьютексы, насыщение CPU, эффект thundering herd.","action":"При всплеске Threads_running найдите медленные/заблокированные запросы (SHOW PROCESSLIST), настройте запросы, ограничьте конкурентность пулом подключений и thread pool и устраните источник конкуренции.","tags":["metric","concurrency","threads","load"]},{"name":"Innodb_row_lock_time_avg","kind":"metric","meaning":"Среднее время (в миллисекундах) ожидания получения блокировки строки; показывает остроту конкуренции блокировок уровня строк.","causes":"Интенсивный одновременный доступ к одним и тем же строкам, долгие транзакции, широкие диапазоны блокировок из-за отсутствия индексов, ожидания, приближающиеся к взаимоблокировке/lock wait timeout.","action":"Сократите длительность транзакций, сузьте область блокировок подходящими индексами, перепроектируйте конфликтный шаблон доступа и пересмотрите innodb_lock_wait_timeout и уровень изоляции.","tags":["metric","innodb","row-lock","contention"]},{"name":"history list length","kind":"metric","meaning":"Число транзакций в undo log InnoDB, еще не очищенных purge (хранящих старые версии строк); показывает накопление старых версий, хранимых для MVCC.","causes":"Долгие открытые транзакции (особенно долгие читающие транзакции), медленный поток purge, высокая частота DML, незафиксированные/простаивающие транзакции.","action":"Выявите и завершите долгие и простаивающие транзакции, настройте число потоков purge (innodb_purge_threads) и делайте транзакции в приложении короткими; высокий HLL ведет к проблемам производительности и bloat.","tags":["metric","innodb","undo","mvcc"]},{"name":"Innodb_data_reads","kind":"metric","meaning":"Общее число запросов физического чтения, выполненных InnoDB с диска; показывает нагрузку чтения, которую буферный пул не смог обслужить и которая ушла на диск.","causes":"Маленький буферный пул, холодный кэш, неэффективные запросы и большие сканирования, отсутствующие индексы, рабочий набор больше памяти.","action":"Увеличьте размер буферного пула, исправьте неэффективные запросы и отсутствующие индексы, отслеживайте тренд физических чтений вместе с коэффициентом попаданий буферного пула и улучшите производительность I/O.","tags":["metric","innodb","io","read"]},{"name":"Innodb_buffer_pool_read_requests","kind":"metric","meaning":"Общее число запросов логического чтения, обслуженных из буферного пула InnoDB; используется вместе с Innodb_buffer_pool_reads (число страниц, не найденных в буферном пуле и прочитанных с диска) для расчета коэффициента попаданий буферного пула.","causes":"Высокое значение указывает на интенсивную активность запросов. Если отношение Innodb_buffer_pool_reads к этому значению велико (низкий коэффициент попаданий), есть нехватка памяти или неэффективный доступ.","action":"Отслеживайте коэффициент попаданий буферного пула (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests); если он низкий, увеличьте буферный пул и оптимизируйте запросы, настройте неэффективные запросы с избыточными логическими чтениями.","tags":["metric","innodb","buffer-pool","logical-io"]}]},"reference/waits-mssql.json":{"engineId":"mssql","engine":"SQL Server","order":4,"waits":[{"name":"CXPACKET","kind":"wait","meaning":"Ожидание, возникающее при синхронизации потоков во время параллельного выполнения запроса; наблюдается, пока ожидается распределение/согласование работы между операторами параллельного плана.","causes":"Высокая степень параллелизма, неравномерное распределение работы (skew), неограниченный/высокий MAXDOP (0) и низкий cost threshold for parallelism (по умолчанию 5), большие параллельные сканирования из-за отсутствия индексов, неэффективные планы.","action":"Настройте cost threshold for parallelism и MAXDOP под нагрузку, исправьте отсутствующие индексы и статистику и не считайте CXPACKET проблемой самой по себе; выделите реальное ожидание с помощью CXCONSUMER и займитесь неэффективными параллельными планами.","tags":["parallelism","cxpacket","query","cpu"]},{"name":"CXCONSUMER","kind":"wait","meaning":"В параллельном плане поток-потребитель ждет поступления данных от потоков-производителей; обычно безвредное ожидание параллелизма.","causes":"Поток-потребитель ждет при обычном параллельном выполнении, неравномерный поток данных, задержки на стороне производителя, неэффективные параллельные планы.","action":"CXCONSUMER в основном безвреден и сам по себе не требует действий; чтобы найти реальное узкое место, сосредоточьтесь на базовых типах ожиданий на стороне производителя (IO, LCK и т.д.) и на эффективности плана.","tags":["parallelism","cxconsumer","query","benign"]},{"name":"PAGEIOLATCH_SH","kind":"wait","meaning":"Поток ждет latch для защиты страницы данных в разделяемом (SH) режиме, пока она читается с диска в буферный пул; ожидание физического I/O при чтении.","causes":"Медленный дисковый I/O, нехватка памяти (маленький буферный пул), большие сканирования и отсутствующие индексы, холодный кэш, высокая нагрузка физического чтения.","action":"Оцените память (max server memory) и буферный пул, исправьте неэффективные запросы и отсутствующие индексы, снизьте задержку I/O хранилища, выявите и настройте самые читающие запросы.","tags":["io","pageiolatch","read","buffer-pool"]},{"name":"PAGEIOLATCH_EX","kind":"wait","meaning":"Поток ждет latch для защиты читаемой с диска страницы данных в монопольном (EX) режиме; обычно ждет физического I/O страницы, которая будет прочитана для изменения.","causes":"Медленный дисковый I/O, интенсивная нагрузка записи, нехватка памяти, давление checkpoint/lazy writer, операции с крупными изменениями данных.","action":"Улучшите производительность записи/чтения хранилища, увеличьте память буферного пула, сделайте интенсивно пишущие операции пакетными и эффективными, разделите и измеряйте I/O дисков tempdb и данных.","tags":["io","pageiolatch","write","buffer-pool"]},{"name":"PAGELATCH_UP","kind":"wait","meaning":"Поток ждет latch для защиты уже находящейся в памяти страницы в режиме обновления (UP); это не физический I/O, а конкуренция за одновременный доступ к странице в памяти.","causes":"Конкуренция за горячие страницы в памяти, особенно за системные страницы tempdb (PFS/GAM/SGAM), интенсивное одновременное создание объектов tempdb, конкуренция вставок в последнюю страницу (hot page).","action":"Разделите tempdb на файлы данных одинакового размера по числу ядер, снизьте конкуренцию в tempdb, распределите шаблоны горячих страниц и устраните лишнее использование tempdb (сбросы и т.д.).","tags":["pagelatch","tempdb","concurrency","contention"]},{"name":"PAGELATCH_EX","kind":"wait","meaning":"Поток ждет latch для защиты страницы в памяти в монопольном (EX) режиме; обычно наблюдается при конкуренции вставок в последнюю страницу на таблицах с возрастающими (последовательными) ключами.","causes":"Конкуренция вставок в последнюю страницу на индексах с последовательными/возрастающими ключами, конкуренция за системные страницы tempdb, интенсивная одновременная запись в горячие страницы, высокая частота вставок.","action":"При конкуренции за последнюю страницу используйте OPTIMIZE_FOR_SEQUENTIAL_KEY = ON (SQL Server 2019+) или распределение ключей (hash/GUID/reverse index), разделите tempdb на несколько файлов и перепроектируйте шаблон интенсивных вставок.","tags":["pagelatch","last-page-insert","concurrency","contention"]},{"name":"LCK_M_X","kind":"wait","meaning":"Поток ждет получения монопольной блокировки ресурса (строки/страницы/таблицы); обычно ждет освобождения других блокировок, чтобы изменить данные (UPDATE/DELETE/INSERT).","causes":"Долгие транзакции, одновременные изменения одних и тех же ресурсов, эскалация блокировок, широкая область блокировок из-за отсутствия индексов, блокирующие сессии.","action":"Делайте транзакции короткими и своевременно выполняйте commit, сузьте область блокировок подходящими индексами, найдите блокирующий запрос (sys.dm_exec_requests/blocking), пересмотрите эскалацию блокировок и уровень изоляции, при необходимости рассмотрите RCSI.","tags":["lock","exclusive","blocking","transaction"]},{"name":"LCK_M_U","kind":"wait","meaning":"Поток ждет получения блокировки обновления ресурса; пока UPDATE сканирует, читая строку и готовясь ее изменить, он ждет освобождения других блокировок.","causes":"Одновременные обновления одних и тех же строк, долгие транзакции, много сканируемых и блокируемых строк из-за отсутствия индексов, блокирующие сессии, конкуренция блокировок.","action":"Добавьте подходящие индексы для операторов UPDATE, чтобы сократить число сканируемых/блокируемых строк, сократите длительность транзакций, проанализируйте цепочку блокировок и оцените уровень изоляции и варианты RCSI.","tags":["lock","update-lock","blocking","transaction"]},{"name":"WRITELOG","kind":"wait","meaning":"Поток ждет записи записей журнала транзакций на диск (log flush); обычно ждет при commit, пока журнал станет надежно записанным.","causes":"Медленный дисковый I/O журнала, очень частые/мелкие commit, высокая нагрузка записи, конкуренция диска журнала и диска данных, влияние синхронной репликации Always On.","action":"Перенесите журнал транзакций на быстрый диск с низкой задержкой, группируйте commit, сократите излишне частые commit, рассмотрите delayed durability (если уместно) и измеряйте задержку I/O журнала.","tags":["log","writelog","commit","io"]},{"name":"ASYNC_NETWORK_IO","kind":"wait","meaning":"SQL Server готов отправить результаты, но ждет на стороне сети/клиента, так как клиент недостаточно быстро потребляет данные; обычно узкое место на стороне приложения.","causes":"Клиентское приложение медленно обрабатывает результаты (построчное потребление), большие наборы результатов, медленная сеть, обработка RBAR в приложении, нехватка ресурсов клиента.","action":"Потребляйте результаты в приложении быстро и пакетно, сократите излишне большие наборы результатов (только нужные столбцы/строки) и проверьте сетевую задержку. Обычно это проблема клиента/сети, а не SQL Server.","tags":["network","client","application","async"]},{"name":"SOS_SCHEDULER_YIELD","kind":"wait","meaning":"Ожидание, возникающее, когда поток исчерпывает свой квант времени CPU и добровольно уступает планировщик; в основном указывает на давление на CPU или CPU-интенсивную обработку.","causes":"Интенсивные CPU-bound запросы, высокая конкурентность и насыщение CPU, конкуренция за spinlock, неэффективные запросы с большим числом логических чтений.","action":"Найдите и настройте неэффективные запросы, потребляющие CPU (отсутствующий индекс, плохой план), сократите логические чтения, оцените емкость CPU и конкурентность; если значение остается высоким, исследуйте узкое место оборудования/плана.","tags":["cpu","scheduler","cpu-bound","yield"]},{"name":"RESOURCE_SEMAPHORE","kind":"wait","meaning":"Запрос ждет выделения памяти (memory grant), необходимой для выполнения; выполнение не может начаться, пока не освободится достаточно памяти запросов.","causes":"Запросы, запрашивающие избыточную память (большие сортировки/хеширования), неверные (завышенные) оценки кардинальности, нехватка памяти сервера, много одновременных запросов с интенсивным использованием памяти, устаревшая статистика.","action":"Обновите статистику, чтобы исправить завышенные оценки memory grant, оптимизируйте запросы с большими сортировками/хешированиями (индекс, фильтр), пересмотрите настройки Resource Governor и max server memory и ограничьте одновременную нагрузку с интенсивным использованием памяти.","tags":["memory","memory-grant","query","resource"]},{"name":"THREADPOOL","kind":"wait","meaning":"Задачи ждут свободный поток из пула потоков, так как нет рабочего потока для выполнения новой задачи; критичный тип ожидания, указывающий на серьезное исчерпание рабочих потоков.","causes":"Потоки, удерживаемые долгими блокировками, очень много одновременных подключений, недостаточный max worker threads, интенсивный параллелизм, взаимоблокировки/длинные цепочки блокировок.","action":"Срочно устраните цепочку блокировок, управляйте одновременными подключениями через пул подключений, устраните долго удерживаемые блокировки, пересмотрите настройку max worker threads; для поиска первопричины подключитесь через DAC.","tags":["threadpool","worker-thread","blocking","critical"]},{"name":"HADR_SYNC_COMMIT","kind":"wait","meaning":"На синхронной реплике Always On primary ждет закрепления (harden) записи журнала на вторичной реплике перед фиксацией транзакции; задержка синхронного commit.","causes":"Медленная сеть/высокая задержка до вторичной реплики, медленный дисковый I/O журнала на вторичной реплике, перегруженная вторичная реплика, географически удаленная реплика, высокая скорость записи.","action":"Снизьте сетевую задержку и увеличьте пропускную способность между репликами, повысьте производительность диска журнала вторичной реплики, сбалансируйте ее нагрузку; если задержка критична, пересмотрите топологию синхронных/асинхронных реплик.","tags":["alwayson","replication","sync-commit","hadr"]},{"name":"Page Life Expectancy","kind":"metric","meaning":"Метрика, показывающая среднее число секунд, которое страница данных остается в буферном пуле (не вытесняясь из памяти); важный индикатор давления на память.","causes":"Низкий или быстро падающий PLE означает давление на память, неэффективные большие сканирования, рабочий набор больше памяти или нехватку памяти. Узлы NUMA также следует оценивать по отдельности.","action":"Смотрите на тренд PLE и резкие падения, а не на фиксированный порог, оцените память буферного пула (max server memory), настройте неэффективные запросы, потребляющие много памяти, и отслеживайте PLE по каждому узлу NUMA.","tags":["metric","memory","buffer-pool","ple"]},{"name":"Коэффициент попаданий в buffer cache","kind":"metric","meaning":"Доля запросов страниц данных, удовлетворенных из буферного кэша без обращения к диску; показывает процент страниц, прочитанных из памяти.","causes":"На современных системах это значение обычно очень высокое (благодаря read-ahead), а низкое значение указывает на значительное давление на память. Высокое значение само по себе не гарантирует хорошую производительность.","action":"Интерпретируйте эту метрику не изолированно, а вместе с PLE, ожиданиями PAGEIOLATCH и трендами физических чтений; при признаках давления на память увеличьте память буферного пула и исправьте неэффективные запросы.","tags":["metric","memory","buffer-pool","ratio"]}]},"reference/waits-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"waits":[{"name":"WiredTiger cache used %","kind":"metric","meaning":"Какая часть выделенного внутреннего кэша движка хранения WiredTiger используется; показывает давление удержания данных в памяти (целевой порог по умолчанию около 80%).","causes":"Рабочий набор больше кэша, кэш недостаточного размера, большие сканирования, интенсивные чтения/записи, много открытых транзакций.","action":"Оцените размер кэша WiredTiger (storage.wiredTiger.engineConfig.cacheSizeGB) и RAM, добавьте индексы, сокращающие рабочий набор, и отслеживайте давление вытеснения и задержки по мере приближения использования к цели 80% и порогу вытеснения 95%.","tags":["metric","wiredtiger","cache","memory"]},{"name":"WiredTiger cache dirty %","kind":"metric","meaning":"Процент данных в кэше WiredTiger, еще не записанных на диск (грязных); показывает давление записи и нагрузку checkpoint/вытеснения (порог вытеснения грязных данных обычно около 20%).","causes":"Высокая нагрузка записи, медленный дисковый I/O, большие/долгие записи, давление контрольных точек, кэш не успевает за скоростью записи.","action":"Если процент грязных данных приближается к порогу вытеснения (20%), улучшите производительность записи на диск, сбалансируйте нагрузку записи, оцените размер кэша и пересмотрите долгие записи и интервал контрольных точек.","tags":["metric","wiredtiger","cache","write"]},{"name":"доступные тикеты (read/write)","kind":"metric","meaning":"Оставшееся число тикетов конкурентности, которые WiredTiger выдает для одновременных операций чтения и записи; если оно падает до нуля, новые операции встают в очередь за тикетом.","causes":"Медленные операции, долго удерживающие тикеты, узкое место дискового I/O, блокировки/конкуренция, внезапные всплески нагрузки, тикеты, исчерпанные долгими запросами.","action":"Если доступных тикетов почти нет и образуется очередь, оптимизируйте медленные запросы (добавьте индексы), улучшите дисковый I/O, сбалансируйте нагрузку; исчерпание тикетов обычно симптом более глубокого узкого места I/O/конкуренции.","tags":["metric","wiredtiger","concurrency","tickets"]},{"name":"очередь глобальной блокировки","kind":"metric","meaning":"Длина очереди операций чтения (readers) и записи (writers), ожидающих глобальную блокировку/блокировку базы данных/коллекции; показывает число операций, ожидающих при конкуренции блокировок.","causes":"Долго удерживаемые блокировки, интенсивная конкуренция записи, долгие операции, DDL и кратковременная монопольная блокировка коллекции в начале/конце построения индекса (гибридное построение 4.2+; foreground-построения больше нет), блокировки, долго удерживаемые из-за медленного I/O.","action":"Если очередь постоянно заполнена, найдите и оптимизируйте долгие операции (currentOp), планируйте построение индексов и другие DDL на часы низкой нагрузки, улучшите I/O и пересмотрите конфликтные шаблоны записи.","tags":["metric","lock","queue","concurrency"]},{"name":"отставание репликации (secondary)","kind":"metric","meaning":"Насколько (в секундах) secondary-член отстает в применении oplog primary; показывает задержку данных между членами replica set.","causes":"Медленная сеть, медленный дисковый I/O на secondary, высокая нагрузка чтения на secondary, высокая скорость записи на primary, недостаточное оборудование secondary, долгие операции.","action":"Отслеживайте отставание через rs.printSecondaryReplicationInfo, улучшите производительность сети и I/O secondary, сбалансируйте нагрузку secondary, поддерживайте достаточный размер oplog и пересмотрите требования write concern.","tags":["metric","replication","lag","secondary"]},{"name":"окно oplog","kind":"metric","meaning":"Промежуток времени (в часах) между самой старой и самой новой операциями в oplog; показывает, насколько долго secondary может отставать без потери синхронизации.","causes":"Маленький размер oplog, внезапная высокая скорость записи, secondary, долго находящиеся офлайн, рост нагрузки репликации.","action":"Если окно oplog сократилось, увеличьте размер oplog, удерживайте отставание secondary ниже окна и пересмотрите нагрузку записи и требования к бэкапу/синхронизации; если окно пропущено, нужна полная ресинхронизация.","tags":["metric","replication","oplog","window"]},{"name":"page faults","kind":"metric","meaning":"Число ошибок страниц (особенно hard faults), когда операционная система должна считать с диска страницы данных, отсутствующие в памяти; показывает вытеснение данных из памяти на диск.","causes":"Рабочий набор больше RAM, нехватка памяти, большие сканирования, давление на кэш, потребление памяти другими процессами.","action":"Оцените память сервера/рабочий набор, сократите объем сканируемых данных, добавив индексы, сбалансируйте кэш WiredTiger и память ОС, отслеживайте рост hard page faults вместе с задержкой I/O.","tags":["metric","memory","page-fault","io"]},{"name":"текущие подключения","kind":"metric","meaning":"Число клиентских подключений, открытых к серверу в данный момент; показывает использование пула подключений и близость к максимуму.","causes":"Неправильно настроенные пулы подключений, утечки подключений, внезапный рост числа клиентов, долго удерживаемые подключения, избыточные подключения от микросервисов.","action":"Сравните число подключений с лимитом сервера (maxIncomingConnections), правильно подберите размер пула подключений на стороне приложения, устраните утечки подключений и отслеживайте внезапные всплески.","tags":["metric","connections","pool","capacity"]},{"name":"доля scanAndOrder / COLLSCAN","kind":"metric","meaning":"Доля запросов, полностью сканирующих коллекцию без индекса (COLLSCAN) или сортирующих в памяти (scanAndOrder); индикатор неэффективности индексов.","causes":"Отсутствующие или неподходящие индексы, ключи сортировки, не соответствующие запросу, широкие запросы без фильтров, плохие шаблоны запросов.","action":"Пересмотрите медленные запросы и их планы (explain), добавьте составные индексы под шаблоны запросов и сортировки, исправьте запросы с COLLSCAN и scanAndOrder и отслеживайте соотношение просмотренных/возвращенных документов.","tags":["metric","index","collscan","query"]},{"name":"операции в очереди","kind":"metric","meaning":"Число операций чтения и записи в очереди (еще не способных выполняться активно), ожидающих блокировку или ресурс; показывает, что система не справляется со спросом.","causes":"Конкуренция блокировок, исчерпание тикетов, узкое место I/O, долгие запросы, внезапные всплески нагрузки, медленные диски.","action":"Если очередь растет, изучите и оптимизируйте активные и долгие операции (currentOp), улучшите индексы и I/O, сбалансируйте нагрузку и найдите первопричину вместе с метриками тикетов и конкуренции блокировок.","tags":["metric","queue","concurrency","load"]}]},"reference/waits-redis.json":{"engineId":"redis","engine":"Redis","order":6,"waits":[{"name":"keyspace hit ratio","kind":"metric","meaning":"Доля поисков ключей, найденных в памяти, вычисляемая из keyspace_hits и keyspace_misses; показывает, насколько эффективно Redis работает как кэш.","causes":"Низкое значение указывает на вытеснение из-за нехватки памяти, короткие TTL, холодный кэш, много поисков уникальных ключей, которые никогда не кэшировались, или плохой дизайн ключей.","action":"Отслеживайте коэффициент попаданий как hits/(hits+misses); если он низкий, пересмотрите память/политику вытеснения и TTL, заранее прогревайте часто запрашиваемые данные, улучшите дизайн ключей и стратегию кэширования.","tags":["metric","cache","hit-ratio","keyspace"]},{"name":"evicted_keys","kind":"metric","meaning":"Число ключей, удаленных из памяти политикой вытеснения при достижении лимита maxmemory; прямой индикатор давления на память.","causes":"Превышение лимита maxmemory, нехватка памяти, накопление ключей без TTL, объем данных больше памяти, неподходящая политика вытеснения.","action":"При стабильном росте вытеснения добавьте память или сократите объем данных, выберите maxmemory-policy (например, allkeys-lru/volatile-lru) под нагрузку и пересмотрите использование TTL и большие ключи.","tags":["metric","memory","eviction","maxmemory"]},{"name":"blocked_clients","kind":"metric","meaning":"Число клиентов, ожидающих событие/данные с блокирующими командами, такими как BLPOP, BRPOP, WAIT и XREAD BLOCK; нормально в шаблонах потребителей очередей, но аномальный рост — тревожный сигнал.","causes":"Потребители, использующие блокирующие команды списков/потоков, отсутствие данных со стороны производителя, ожидание подтверждения реплик через WAIT, дисбаланс потребителей и производителей.","action":"Сравните число заблокированных клиентов с ожидаемой моделью потребителей; при аномальном росте исследуйте сторону производителя и подтверждения репликации и сбалансируйте скорость потребления очереди.","tags":["metric","clients","blocking","queue"]},{"name":"mem_fragmentation_ratio","kind":"metric","meaning":"Отношение физической памяти, выделенной Redis операционной системой (RSS), к памяти, фактически используемой Redis; показывает фрагментацию памяти или своппинг.","causes":"Значения заметно выше 1 указывают на фрагментацию памяти, а значения ниже 1 — на то, что Redis выгружен в своп (на диск). Ключи переменного размера и циклы удаления/вставки увеличивают фрагментацию.","action":"При высокой фрагментации рассмотрите включение activedefrag или перезапуск; если значение ниже 1, исследуйте своппинг и добавьте память; пересмотрите поведение аллокатора (jemalloc) и шаблоны больших ключей.","tags":["metric","memory","fragmentation","swap"]},{"name":"instantaneous_ops_per_sec","kind":"metric","meaning":"Число команд, которые Redis обрабатывает в секунду прямо сейчас (мгновенная пропускная способность); показывает интенсивность нагрузки и внезапные изменения трафика.","causes":"Внезапные всплески трафика, нагрузка пакетных заданий, неэффективные шаблоны доступа клиентов, отказ от pipelining в пользу отдельных команд, интенсивные всплески чтения/записи.","action":"Отслеживайте пропускную способность вместе с задержкой и CPU, сопоставляйте внезапные падения/всплески с трафиком и медленными командами, повышайте эффективность через pipelining/batching и планируйте емкость.","tags":["metric","throughput","ops","load"]},{"name":"rdb_last_bgsave_status","kind":"metric","meaning":"Статус успешности последнего фонового сохранения RDB (BGSAVE); показывает, был ли снимок персистентности сделан успешно.","causes":"Статус ошибки (err) может быть вызван заполненным диском/нехваткой места, нехваткой памяти для fork, ошибкой дискового I/O, проблемами прав или чрезмерной нагрузкой.","action":"Если статус 'err', проверьте место на диске, память, необходимую для fork (настройка overcommit), и состояние диска, просмотрите журналы; неудачный BGSAVE создает риск потери данных, поэтому пересмотрите конфигурацию персистентности.","tags":["metric","persistence","rdb","bgsave"]},{"name":"master_link_status","kind":"metric","meaning":"Статус (up/down) канала репликации реплики к master; показывает, исправно ли соединение между репликой и master.","causes":"Статус 'down' может быть результатом сетевого сбоя, недоступности master, ошибки синхронизации репликации, проблемы аутентификации или перегрузки master.","action":"Если статус 'down', проверьте сетевую связность, доступность master и настройки auth, просмотрите журналы реплики, отслеживайте отставание master_repl_offset и убедитесь, что синхронизация завершена.","tags":["metric","replication","master-link","availability"]},{"name":"задержка (slowlog)","kind":"metric","meaning":"Задержка медленных команд, превышающих заданный порог, выявляемая через slowlog и мониторинг задержек; в однопоточном Redis показывает риск того, что одна команда блокирует остальные.","causes":"Команды с высокой временной сложностью (KEYS, большой SORT, SMEMBERS/HGETALL на больших коллекциях), большие ключи, медленные Lua-скрипты, паузы fork/персистентности, своппинг.","action":"Найдите медленные команды через SLOWLOG и LATENCY, устраните команды O(N) (SCAN вместо KEYS) и большие ключи, оптимизируйте скрипты и отслеживайте паузы, вызванные fork/AOF/RDB.","tags":["metric","latency","slowlog","performance"]},{"name":"expired_keys","kind":"metric","meaning":"Общее число ключей, удаленных из-за истечения TTL; индикатор поведения истечения по TTL и освобождения памяти.","causes":"Истечение ключей с TTL — нормальное явление. Резкие изменения могут указывать на смену политики TTL, массовое истечение ключей или неожиданные шаблоны удаления.","action":"Различайте expired и evicted (expired — нормальное поведение TTL, evicted — давление на память), отслеживайте влияние внезапных волн истечения на промахи кэша и нагрузку и распределяйте TTL.","tags":["metric","ttl","expiration","keyspace"]},{"name":"connected_clients vs maxclients","kind":"metric","meaning":"Насколько текущее число подключенных клиентов близко к настроенному лимиту maxclients; показывает риск исчерпания емкости подключений.","causes":"Утечки подключений, неправильно настроенный пул подключений, внезапный рост числа клиентов, долго удерживаемые простаивающие подключения, много подключений микросервисов.","action":"Сравните число подключенных клиентов с maxclients; при приближении к лимиту правильно подберите размер пула подключений, устраните утечки, задайте тайм-аут простоя подключений и при необходимости увеличьте maxclients.","tags":["metric","connections","maxclients","capacity"]}]},"reference/waits-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"waits":[{"name":"JVM heap used %","kind":"metric","meaning":"Процент используемого heap JVM узла; высокий, постоянно заполненный heap означает давление на память и риск тяжелого GC.","causes":"Избыточная память fielddata/агрегаций, слишком много шардов, высокая нагрузка bulk/запросов, большие mappings, heap недостаточного размера, шаблоны утечки памяти.","action":"Отслеживайте использование heap (вмешивайтесь, особенно если после GC оно остается выше 75%), сократите число шардов, ограничьте тяжелые агрегации и использование fielddata, держите heap не более половины RAM и ниже 32GB.","tags":["metric","jvm","heap","memory"]},{"name":"длительность/частота GC","kind":"metric","meaning":"Длительность и частота сборки мусора JVM; долгие/частые паузы GC делают узел неотзывчивым и ухудшают производительность.","causes":"Высокое давление на heap, большое накопление в старом поколении (old gen), тяжелые агрегации/fielddata, heap недостаточного размера, слишком много шардов, долгие большие запросы.","action":"Устраните давление на heap, являющееся первопричиной долгих пауз (особенно old GC), оптимизируйте запросы, требовательные к памяти, сократите число шардов; частый и долгий GC — ранний признак нестабильности узла.","tags":["metric","jvm","gc","pause"]},{"name":"thread_pool write rejected","kind":"metric","meaning":"Число запросов индексирования, отклоненных из-за заполненной очереди пула потоков write (indexing/bulk); показывает, что узел не справляется с нагрузкой записи.","causes":"Избыточная скорость bulk/индексирования, медленный дисковый I/O, много мелких запросов вместо bulk, недостаточная емкость узла, давление refresh.","action":"Отправляйте запросы индексирования пакетами bulk подходящего размера, ограничивайте скорость записи по емкости узла (backoff), улучшите дисковый I/O, увеличьте refresh_interval, масштабируйте емкость.","tags":["metric","thread-pool","indexing","rejection"]},{"name":"thread_pool search rejected","kind":"metric","meaning":"Число поисковых запросов, отклоненных из-за заполненной очереди пула потоков search; показывает, что узел не справляется с нагрузкой запросов.","causes":"Избыточное число одновременных запросов, тяжелые/медленные агрегации, затрагивание слишком многих шардов, неэффективные запросы, недостаточная емкость узлов/CPU.","action":"Ограничьте конкурентность запросов на стороне клиента, оптимизируйте тяжелые запросы и агрегации, сократите лишний fan-out по шардам (routing/фильтрация), добавьте узлы и увеличьте число шардов-реплик (поиск обслуживается и шардами-репликами), масштабируйте емкость.","tags":["metric","thread-pool","search","rejection"]},{"name":"задержка индексирования/поиска","kind":"metric","meaning":"Средняя задержка операций индексирования и поиска; вычисляется из метрик index_time_in_millis и query_time_in_millis и отражает время отклика, видимое пользователям.","causes":"Медленный дисковый I/O, тяжелые запросы/агрегации, паузы GC, большое число шардов, давление refresh/merge, нехватка ресурсов.","action":"Найдите и оптимизируйте медленные запросы через slow log, настройте bulk и refresh_interval для индексирования, улучшите производительность диска и heap и интерпретируйте задержку вместе с отказами пулов потоков и GC.","tags":["metric","latency","indexing","search"]},{"name":"время refresh/flush","kind":"metric","meaning":"Время, затрачиваемое на refresh, делающий новые документы доступными для поиска, и на flush, сохраняющий translog в Lucene; показывает накладные расходы индексирования.","causes":"Слишком частые refresh (низкий refresh_interval), медленный дисковый I/O, интенсивное индексирование, большой translog, много мелких сегментов.","action":"При интенсивном индексировании увеличьте refresh_interval (например, 30s) или временно отключите его, улучшите дисковый I/O, настройте размер bulk, пересмотрите поведение слияния сегментов.","tags":["metric","refresh","flush","indexing"]},{"name":"неназначенные шарды","kind":"metric","meaning":"Число шардов, не назначенных ни одному узлу; неназначенный первичный шард означает недоступность данных (статус red), неназначенная реплика — риск для надежности/емкости.","causes":"Сбой/выход узла, превышение disk watermark, слишком высокое число реплик для числа узлов, правила/фильтры размещения, задержки при восстановлении шардов.","action":"Найдите причину через cluster allocation explain API, восстановите сбойные узлы, устраните проблемы места на диске/watermark, пересмотрите число реплик и правила размещения; в первую очередь займитесь статусом red.","tags":["metric","shards","allocation","cluster-health"]},{"name":"disk watermark %","kind":"metric","meaning":"Использование диска узла относительно порогов low/high/flood-stage (по умолчанию 85%/90%/95%), влияющих на размещение шардов Elasticsearch.","causes":"Заполнение диска, рост данных, нехватка хранилища, несбалансированное распределение шардов, неочищаемые старые индексы.","action":"При low watermark новые шарды на узел не назначаются; при high watermark шарды перемещаются на другие узлы; при flood-stage индексы становятся только для чтения. Освободите место на диске (удалите старые индексы/используйте ILM), расширьте хранилище, сбалансируйте распределение шардов и отслеживайте пороги watermark.","tags":["metric","disk","watermark","storage"]},{"name":"вытеснения из кэшей fielddata/query","kind":"metric","meaning":"Число записей, вытесненных из кэша fielddata и кэша запросов (filter) из-за лимитов памяти; указывает на неэффективность кэша и давление на память.","causes":"Высокое использование fielddata (сортировка/агрегации по текстовым полям), маленькие лимиты кэша, сильно различающиеся фильтры запросов, давление на память, фильтры, которые не используются повторно.","action":"Используйте keyword/doc_values вместо fielddata на текстовых полях, ограничьте тяжелые агрегации по fielddata, пересмотрите размеры кэшей; частые вытеснения оценивайте вместе с давлением на heap.","tags":["metric","cache","fielddata","eviction"]},{"name":"срабатывания circuit breaker","kind":"metric","meaning":"Сколько раз circuit breaker (parent/fielddata/request breaker) предсказывал, что запрос превысит память, и отклонял его; защитный механизм, оберегающий узел от OOM.","causes":"Агрегации/запросы, запрашивающие избыточную память, высокое использование fielddata, большие bulk-запросы, общее давление на heap, очень большие наборы результатов.","action":"Рассматривайте срабатывания circuit breaker как признак давления на heap, оптимизируйте запросы, требовательные к памяти, сократите использование fielddata/агрегаций, уменьшите размер bulk и пересмотрите heap и число шардов.","tags":["metric","circuit-breaker","memory","oom"]},{"name":"pending tasks","kind":"metric","meaning":"Очередь задач обновления cluster state, ожидающих обработки master-узлом; показывает, что master не справляется с нагрузкой управления кластером.","causes":"Большое число индексов/шардов, частые изменения mapping/settings, недостаточные ресурсы master-узла, большой cluster state, проблемы сети/выбора master.","action":"Если очередь постоянно заполнена, сократите число шардов/индексов, ограничьте частые динамические обновления mapping, используйте отдельные выделенные master-узлы и пересмотрите размер cluster state и ресурсы master.","tags":["metric","master","cluster-state","queue"]}]},"reference/alerts-oracle.json":{"engineId":"oracle","engine":"Oracle Database","order":1,"alerts":[{"metric":"Коэффициент попаданий в buffer cache","warn":"< 95%","crit":"< 90%","tool":"V$SYSSTAT / V$BUFFER_POOL_STATISTICS","note":"Коэффициент попаданий в буферный кэш показывает процент блоков данных, прочитанных из памяти без обращения к диску. Низкие значения указывают на рост физических чтений и недостаточный размер SGA. Сам по себе он может вводить в заблуждение; всегда оценивайте его вместе с физическими чтениями в отчете AWR.","tags":["memory","cache","sga"]},{"metric":"Использование табличного пространства (%used)","warn":"> 85%","crit":"> 95%","tool":"DBA_TABLESPACE_USAGE_METRICS","note":"Если использование табличного пространства достигает 100%, новые экстенты выделить нельзя и операции останавливаются с ошибками вроде ORA-01653. Даже при включенном autoextend следите за лимитом диска. Представление DBA_TABLESPACE_USAGE_METRICS дает правильный процент как для автоматически расширяемых файлов, так и для файлов фиксированного размера.","tags":["disk","tablespace","capacity"]},{"metric":"Использование памяти PGA","warn":"> 90% pga_aggregate_target","crit":"> 100% (over-alloc)","tool":"V$PGASTAT / V$PGA_TARGET_ADVICE","note":"При превышении цели PGA операции сортировки и хеширования сбрасываются на диск (temp), и запросы замедляются. Постоянно растущий over allocation count показывает, что цель недостаточна. Пересмотрите настройку pga_aggregate_target относительно памяти сервера.","tags":["memory","pga","temp"]},{"metric":"Использование табличного пространства TEMP","warn":"> 80%","crit":"> 95%","tool":"V$TEMPSEG_USAGE / V$SORT_SEGMENT / DBA_TEMP_FREE_SPACE","note":"Пространство TEMP используется для больших сортировок, hash join и операций с глобальными временными таблицами. При его заполнении возникает ORA-01652 и запросы завершаются с ошибкой. Внезапное заполнение обычно вызвано одним запросом с плохим планом или сортирующим слишком много данных.","tags":["temp","disk","sort"]},{"metric":"Average Active Sessions (AAS)","warn":"> числа ядер CPU","crit":"> 2x числа ядер CPU","tool":"V$ACTIVE_SESSION_HISTORY / OEM","note":"Average Active Sessions выражает текущую нагрузку базы данных одним числом. Если значение превышает число ядер CPU, сессии стоят в очереди за CPU или на событиях ожидания. Если оно держится на уровне, кратном числу ядер, нужен анализ узких мест.","tags":["cpu","load","session"]},{"metric":"Отставание применения Data Guard (apply lag)","warn":"> 60 s","crit":"> 300 s","tool":"V$DATAGUARD_STATS (apply lag)","note":"Apply lag — это насколько standby-база отстает от primary. Высокий apply lag увеличивает объем redo, который нужно применить при failover, и тем самым удлиняет время восстановления (RTO); если redo дошел до standby, данные не теряются. Потенциальная потеря данных (RPO) определяется transport lag. Рассматривайте его вместе с transport lag, чтобы понять, что медленнее — сеть или применение redo.","tags":["replication","dataguard","lag"]},{"metric":"Использование Fast Recovery Area (FRA)","warn":"> 85%","crit":"> 95%","tool":"V$RECOVERY_FILE_DEST / V$RECOVERY_AREA_USAGE","note":"При заполнении FRA генерация archivelog останавливается и вся база данных зависает. Это одна из самых частых причин простоя продуктивных систем. Регулярно проверяйте политики удаления RMAN и значение db_recovery_file_dest_size.","tags":["disk","fra","archivelog"]},{"metric":"Число блокирующих сессий","warn":">= 1 (> 30 s)","crit":">= 1 (> 120 s)","tool":"V$LOCK / V$SESSION (blocking_session)","note":"Когда блокировка, удерживаемая одной сессией, заставляет ждать другие сессии, по всему приложению каскадно распространяется замедление. Долгая блокировка обычно вызвана незафиксированными транзакциями или ошибками приложения. Найдите корневую сессию цепочки блокировок и при необходимости завершите ее.","tags":["lock","blocking","concurrency"]},{"metric":"Число невалидных объектов","warn":"> 0","crit":"> 50","tool":"DBA_OBJECTS (status='INVALID')","note":"Невалидные пакеты, представления или триггеры могут вызывать неожиданные ошибки во время выполнения. Обычно они появляются после изменения DDL. Устраняйте их перекомпиляцией через UTL_RECOMP.RECOMP_PARALLEL или utlrp.sql и исследуйте первопричину постоянно невалидных объектов.","tags":["object","compile","maintenance"]},{"metric":"Самый долгий запрос","warn":"> 5 min","crit":"> 30 min","tool":"V$SQL / V$SESSION_LONGOPS","note":"Один долгий запрос может монополизировать ресурсы и замедлить другие операции. Кроме пакетных заданий, запросы, выполняющиеся минутами в OLTP-системах, обычно вызваны отсутствующим индексом или плохим планом. Изучите план и фактическое число строк в отчете SQL Monitor.","tags":["query","performance","sql"]},{"metric":"Ошибки ORA в alert log","warn":">= 1 (некритичные)","crit":"ORA-00600 / ORA-07445 / ORA-04031","tool":"alert log / V$DIAG_ALERT_EXT","note":"Alert log — журнал состояния базы данных. ORA-00600 и ORA-07445 — внутренние ошибки, которые могут указывать на повреждение данных, а ORA-04031 указывает на исчерпание памяти shared pool. При появлении этих ошибок немедленно исследуйте их и при необходимости передайте в Oracle Support.","tags":["error","alertlog","monitoring"]},{"metric":"Частота переключения redo log","warn":"> 6 / час","crit":"> 12 / час","tool":"V$LOG_HISTORY","note":"Очень частые переключения redo log указывают на недостаточный размер файлов журнала и давление контрольных точек. В идеале в пиковые часы ожидается одно переключение каждые 15-20 минут. Слишком частые переключения увеличивают нагрузку IO и архивации; рассмотрите увеличение групп redo log.","tags":["redo","io","checkpoint"]},{"metric":"Возраст последнего успешного бэкапа RMAN","warn":"> 24 часа","crit":"> 48 часов","tool":"V$RMAN_BACKUP_JOB_DETAILS","note":"Возраст бэкапа определяет самую позднюю точку, до которой можно восстановиться, и напрямую отражает риск потери данных. Если неудачные или пропущенные бэкапы остаются незамеченными, при аварии пути назад нет. Отдельно отслеживайте также бэкапы archivelog, чтобы гарантировать возможность восстановления на момент времени.","tags":["backup","rman","recovery"]},{"metric":"Задержка дискового IO (db file sequential read)","warn":"> 10 ms","crit":"> 20 ms","tool":"V$SYSTEM_EVENT / AWR","note":"Среднее время ожидания одноблочных чтений показывает производительность уровня хранения. На современных flash/SSD-системах это значение должно составлять несколько миллисекунд. Постоянно высокая задержка указывает на узкое место хранилища или избыточные физические чтения; оценивайте SGA и уровень хранения вместе.","tags":["io","disk","latency"]}]},"reference/alerts-postgres.json":{"engineId":"postgres","engine":"PostgreSQL","order":2,"alerts":[{"metric":"Коэффициент попаданий в кэш","warn":"< 99%","crit":"< 95%","tool":"pg_stat_database / postgres_exporter","note":"Коэффициент попаданий в кэш — процент блоков, обслуженных только из shared_buffers; чтения, обслуженные кэшем операционной системы, тоже считаются как blks_read, поэтому соотношение может представлять фактические чтения с диска хуже, чем они есть. В здоровых OLTP-системах PostgreSQL это значение должно быть выше 99%. Низкие значения указывают на нехватку памяти или холодный кэш; вычисляется из blks_hit и blks_read.","tags":["memory","cache"]},{"metric":"Число подключений (connections vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"pg_stat_activity / max_connections","note":"Приближение активных подключений к лимиту max_connections ведет к ошибкам новых подключений. Поскольку каждое подключение расходует память, большое их число также создает давление на память. Рекомендуется держать число backend-процессов низким с помощью пулера подключений, например PgBouncer.","tags":["connection","limit","pool"]},{"metric":"Отставание репликации","warn":"> 10 MB","crit":"> 100 MB","tool":"pg_stat_replication (replay_lag / lsn diff)","note":"Отставание репликации — объем WAL в байтах, на который standby отстает от primary. Высокое отставание означает устаревшие данные на читающих репликах и риск потери данных при failover. Отслеживайте разницу LSN в байтах вместе с длительностью replay_lag.","tags":["replication","lag","standby"]},{"metric":"Возраст самой старой транзакции","warn":"> 5 min","crit":"> 30 min","tool":"pg_stat_activity (xact_start)","note":"Долгие открытые транзакции не дают VACUUM удалять мертвые кортежи и ведут к bloat таблиц. Сессии в состоянии 'idle in transaction' также без нужды удерживают блокировки. Выявляйте такие сессии и ограничивайте их через idle_in_transaction_session_timeout.","tags":["query","vacuum","transaction"]},{"metric":"Взаимоблокировки","warn":"> 1 / min","crit":"> 5 / min","tool":"pg_stat_database (deadlocks)","note":"Взаимоблокировка возникает, когда две транзакции ждут блокировок друг друга, и PostgreSQL разрешает ее, отменяя одну из них. Отдельные взаимоблокировки возможны, но частые указывают на несогласованный порядок блокировок в приложении. Включите log_lock_waits, чтобы увидеть конфликтующие запросы.","tags":["lock","deadlock","concurrency"]},{"metric":"Использование временных файлов","warn":"> 1 GB / час","crit":"> 10 GB / час","tool":"pg_stat_database (temp_bytes)","note":"Временные файлы создаются, когда операции сортировки и хеширования, не помещающиеся в work_mem, сбрасываются на диск. Высокое использование temp вызывает и замедление, и давление на диск. Для запросов, которые часто сбрасываются на диск, рассмотрите увеличение work_mem или оптимизацию запроса.","tags":["temp","disk","work_mem"]},{"metric":"Отставание autovacuum (мертвые кортежи)","warn":"> 10% мертвых кортежей","crit":"> 20% мертвых кортежей","tool":"pg_stat_user_tables (n_dead_tup)","note":"Растущая доля мертвых кортежей показывает, что autovacuum не успевает и bloat таблиц растет. Раздутые таблицы и занимают место, и замедляют сканирования. На нагруженных таблицах снизьте autovacuum_vacuum_scale_factor, чтобы очистка выполнялась чаще.","tags":["vacuum","bloat","maintenance"]},{"metric":"Использование диска (каталог данных)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"PostgreSQL не имеет механизма самозащиты до заполнения диска. При заполнении диска данных запись завершается ошибкой 'could not extend file' / 53100; если заполняется диск WAL (pg_wal), сервер останавливается с PANIC. WAL и временные файлы могут вызывать резкий рост; раннее вмешательство критично.","tags":["disk","capacity"]},{"metric":"Удержание WAL (отставание слота репликации)","warn":"> 1 GB","crit":"> 10 GB","tool":"pg_replication_slots (restart_lsn)","note":"Неиспользуемый или отстающий слот репликации не дает удалять файлы WAL и раздувает каталог pg_wal. Это может закончиться заполнением диска. Выявляйте и удаляйте мертвые слоты и задайте верхнюю границу через max_slot_wal_keep_size.","tags":["wal","slot","disk"]},{"metric":"Частота контрольных точек (requested checkpoints)","warn":"> 10% доля requested","crit":"> 30% доля requested","tool":"PG16 и ранее: pg_stat_bgwriter (checkpoints_req) / PG17+: pg_stat_checkpointer (num_requested)","note":"Когда растет доля запрошенных контрольных точек относительно плановых, max_wal_size слишком мал. Частые контрольные точки вызывают всплески IO и снижение производительности. Пересмотрите настройки max_wal_size и checkpoint_completion_target, чтобы распределить нагрузку.","tags":["checkpoint","wal","io"]},{"metric":"Возраст последнего успешного бэкапа","warn":"> 24 часа","crit":"> 48 часов","tool":"pgBackRest / barman info","note":"Возраст бэкапа определяет последнюю точку, к которой можно вернуться, и напрямую выражает ваш RPO. Для восстановления на момент времени нужны непрерывная цепочка бэкапов и архив WAL. Бэкапы нужно не только делать; их пригодность также нужно проверять регулярными тестами восстановления.","tags":["backup","recovery","rpo"]},{"metric":"Задержка дискового IO","warn":"> 10 ms","crit":"> 20 ms","tool":"node_exporter / iostat","note":"Задержка чтения/записи хранилища напрямую влияет на время выполнения запросов. На современных SSD-системах это значение должно составлять несколько миллисекунд. Устойчиво высокая задержка указывает на узкое место диска или избыточные физические чтения; оценивайте shared_buffers и уровень хранения вместе.","tags":["io","disk","latency"]},{"metric":"Переполнение ID транзакций (age)","warn":"> 1 млрд","crit":"> 1,5 млрд","tool":"pg_database (age(datfrozenxid))","note":"Если возраст ID транзакций приближается к 2 миллиардам, PostgreSQL останавливает всю запись, чтобы предотвратить потерю данных (защита от wraparound). Это один из самых опасных сбоев PostgreSQL. Отслеживайте возраст datfrozenxid и убедитесь, что autovacuum не отстает в работе freeze.","tags":["wraparound","vacuum","critical"]}]},"reference/alerts-mysql.json":{"engineId":"mysql","engine":"MySQL","order":3,"alerts":[{"metric":"Коэффициент попаданий InnoDB buffer pool","warn":"< 99%","crit":"< 95%","tool":"SHOW ENGINE INNODB STATUS / mysqld_exporter","note":"Коэффициент попаданий буферного пула — процент страниц данных и индексов, прочитанных из памяти без обращения к диску. Низкие значения указывают, что innodb_buffer_pool_size слишком мал для рабочего набора. На выделенном сервере буферному пулу обычно отводят ~70-80% памяти (на общем сервере меньше).","tags":["memory","cache","innodb"]},{"metric":"Число подключений (Threads_connected vs max_connections)","warn":"> 80%","crit":"> 90%","tool":"SHOW STATUS / max_connections","note":"По мере приближения числа подключений к лимиту max_connections новые подключения отклоняются с 'Too many connections'. Max_used_connections показывает исторический пик. Держите одновременные подключения под контролем с помощью пула подключений на стороне приложения.","tags":["connections","limit","thread"]},{"metric":"Отставание реплики (Seconds_Behind_Source)","warn":"> 30 s","crit":"> 300 s","tool":"SHOW REPLICA STATUS","note":"Seconds_Behind_Source показывает в секундах, насколько реплика отстает от источника. Высокое отставание означает устаревшие данные на читающих репликах и риск при failover. При однопоточном применении значение может вводить в заблуждение во время больших транзакций; проверяйте его вместе с разрывом GTID.","tags":["replication","lag","replica"]},{"metric":"Время ожидания блокировки строк InnoDB","warn":"avg > 1 s","crit":"avg > 5 s","tool":"SHOW STATUS (Innodb_row_lock_time_avg)","note":"Среднее время ожидания блокировки строк показывает, насколько транзакции заставляют друг друга ждать. Высокие значения указывают на сильно конфликтующие обновления или долгие транзакции. Рассматривайте его вместе с Innodb_row_lock_waits и находите конфликтующие запросы в performance_schema.","tags":["lock","blocking","innodb"]},{"metric":"History list length (undo)","warn":"> 100000","crit":"> 1000000","tool":"SHOW ENGINE INNODB STATUS","note":"History list length — число неочищенных записей undo; растет из-за долгих открытых транзакций. Высокое значение показывает, что purge отстает и производительность чтения ухудшается из-за MVCC. Найдите и завершите долгие транзакции, чтобы список мог сократиться.","tags":["undo","transaction","mvcc"]},{"metric":"Неудачные подключения (Aborted_connects)","warn":"> 5 / min","crit":"> 20 / min","tool":"SHOW STATUS (Aborted_connects)","note":"Aborted_connects считает подключения, завершившиеся неудачей при аутентификации или handshake. Резкие всплески могут указывать на неверные пароли, сетевые проблемы или возможную попытку атаки. Рассматривайте его вместе с Aborted_clients, чтобы определить, где обрывается подключение.","tags":["connections","error","security"]},{"metric":"Временные таблицы, записанные на диск","warn":"> 25% доля","crit":"> 50% доля","tool":"SHOW STATUS (Created_tmp_disk_tables)","note":"Доля временных таблиц в памяти, сбрасываемых на диск, показывает, что запросы превышают лимит tmp_table_size. Временные таблицы на диске заметно замедляют запросы. Оптимизируйте запросы с GROUP BY и столбцами BLOB/TEXT и рассмотрите увеличение tmp_table_size и max_heap_table_size.","tags":["temp","disk","query"]},{"metric":"Медленные запросы","warn":"> 5 / min","crit":"> 20 / min","tool":"slow query log / статус Slow_queries","note":"Запросы, превышающие порог long_query_time, записываются в журнал медленных запросов и являются главным источником проблем производительности. Частые медленные запросы указывают на отсутствующие индексы или плохо написанные запросы. Анализируйте журнал через pt-query-digest и в первую очередь займитесь самыми дорогими запросами.","tags":["query","performance","slowlog"]},{"metric":"Использование CPU","warn":"> 80%","crit":"> 90%","tool":"node_exporter / top","note":"Устойчиво высокое использование CPU может быть вызвано тяжелыми запросами, отсутствующими индексами или слишком большим числом подключений. Короткие пики нормальны, но устойчивое насыщение приводит к очередям запросов. Выявите запросы, больше всего потребляющие CPU, через performance_schema или slow log.","tags":["cpu","load"]},{"metric":"Использование диска (каталог данных)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"Заполненный каталог данных не дает MySQL выполнять запись и вызывает ошибки таблиц. Рост файлов binlog, табличных пространств undo и временного табличного пространства (ibtmp1) может внезапно заполнить диск; redo log имеет фиксированный размер (8.0.30+ #innodb_redo, innodb_redo_log_capacity). Убедитесь, что старые binlog очищаются через binlog_expire_logs_seconds.","tags":["disk","capacity","binlog"]},{"metric":"Самый долгий запрос","warn":"> 60 s","crit":"> 300 s","tool":"SHOW PROCESSLIST / information_schema.PROCESSLIST","note":"Один долгий запрос может влиять на другие транзакции, удерживая блокировки и расходуя ресурсы. В OLTP-системах запросы, выполняющиеся минутами, обычно вызваны плохим планом. Изучите план через EXPLAIN и при необходимости завершите запрос (kill) и оптимизируйте его.","tags":["query","performance"]},{"metric":"Возраст последнего успешного бэкапа","warn":"> 24 часа","crit":"> 48 часов","tool":"xtrabackup / журнал задания mysqldump","note":"Возраст бэкапа определяет последнюю точку, к которой можно вернуться, а значит, и RPO. Вместе с бэкапами binlog становится возможным восстановление на момент времени. Просто делать бэкапы недостаточно; проверяйте их пригодность регулярными тестами восстановления.","tags":["backup","recovery","rpo"]},{"metric":"Задержка дискового IO","warn":"> 10 ms","crit":"> 20 ms","tool":"iostat / node_exporter","note":"Задержка хранилища напрямую переходит во время выполнения запросов и особенно критична для нагрузок, не помещающихся в буферный пул. На современных SSD-системах ожидается несколько миллисекунд. Высокая задержка указывает на узкое место диска или избыточные физические чтения; также пересмотрите размер буферного пула.","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 показывает в секундах оценочное время, которое страница данных может оставаться в памяти до вытеснения из буферного пула. Низкие значения указывают на давление на память и быстрое вытеснение страниц из кэша. Отслеживайте его отдельно для каждого узла NUMA и рассматривайте резкие падения как нехватку памяти.","tags":["memory","cache","ple"]},{"metric":"Коэффициент попаданий в buffer cache","warn":"< 98%","crit":"< 95%","tool":"sys.dm_os_performance_counters","note":"Коэффициент попаданий в буферный кэш — процент страниц, прочитанных из памяти без обращения к диску. В SQL Server это значение обычно очень высокое; оценивайте его вместе с PLE. Поскольку само по себе оно может вводить в заблуждение, при анализе узкого места памяти используйте PLE как основную метрику.","tags":["memory","cache"]},{"metric":"Заблокированные процессы","warn":">= 1 (> 5 s)","crit":">= 1 (> 30 s)","tool":"sys.dm_exec_requests (blocking_session_id)","note":"Когда блокировка, удерживаемая одной сессией, заставляет ждать другие, образуется цепочка блокировок и следует замедление всего приложения. Долгая блокировка обычно вызвана незафиксированными транзакциями или неудачным уровнем изоляции. Определите головную блокирующую сессию с помощью blocked process report.","tags":["lock","blocking","concurrency"]},{"metric":"Использование журнала транзакций (%used)","warn":"> 75%","crit":"> 90%","tool":"sys.dm_db_log_space_usage / DBCC SQLPERF(LOGSPACE)","note":"При заполнении журнала транзакций база данных не может выполнять запись и останавливается с ошибкой 9002. В модели восстановления FULL журнал продолжает расти, если не делаются бэкапы журнала. Отслеживайте частоту бэкапов журнала и число VLF, чтобы убедиться, что журнал усекается и используется повторно.","tags":["log","disk","recovery"]},{"metric":"Использование tempdb (%used)","warn":"> 80%","crit":"> 95%","tool":"sys.dm_db_file_space_usage","note":"tempdb используется совместно для временных таблиц, сортировок, version store и операций хеширования. При его заполнении запросы по всему серверу завершаются ошибкой. Резкий рост обычно вызван большим запросом или долгой snapshot-транзакцией; отслеживайте размер version store отдельно.","tags":["tempdb","disk","sort"]},{"metric":"Самый долгий запрос","warn":"> 60 s","crit":"> 300 s","tool":"sys.dm_exec_requests / Query Store","note":"Один долгий запрос может влиять на другую работу, удерживая блокировки и расходуя ресурсы. В OLTP-системах запросы, выполняющиеся минутами, обычно вызваны отсутствующим индексом, parameter sniffing или плохим планом. Исследуйте регрессии планов с помощью Query Store.","tags":["query","performance"]},{"metric":"Очередь redo в Availability Group","warn":"> 1 MB","crit":"> 100 MB","tool":"sys.dm_hadr_database_replica_states","note":"Очередь redo показывает объем журнала на вторичной реплике, еще ожидающего применения. Большая очередь redo удлиняет время восстановления после failover (RTO) и означает устаревшие данные на читаемых репликах. Изучайте ее вместе с redo_rate, чтобы предсказать, когда отставание будет ликвидировано.","tags":["replication","ag","lag"]},{"metric":"Частота взаимоблокировок","warn":"> 1 / min","crit":"> 5 / min","tool":"system_health XEvent / счетчики производительности","note":"Взаимоблокировка возникает, когда две транзакции ждут блокировок друг друга, и SQL Server разрешает ее, выбирая одну из них жертвой. Отдельные взаимоблокировки возможны, но частые указывают на несогласованный порядок блокировок. Извлеките граф взаимоблокировки из сессии extended event system_health и исправьте конфликтующие запросы.","tags":["lock","deadlock","concurrency"]},{"metric":"Использование рабочих потоков","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_schedulers / max worker threads","note":"При исчерпании пула рабочих потоков новые запросы встают в очередь на ожидании THREADPOOL, и сервер может перестать отвечать. Обычно это вызвано долгими блокировками или избыточными одновременными подключениями. Снизьте давление на потоки, устранив цепочки блокировок.","tags":["cpu","thread","load"]},{"metric":"Использование CPU","warn":"> 80%","crit":"> 90%","tool":"sys.dm_os_ring_buffers / Performance Monitor","note":"Устойчиво высокий CPU может быть вызван тяжелыми запросами, отсутствующими индексами или плохими планами. Короткие пики нормальны, но устойчивое насыщение приводит к очередям запросов. Выявите запросы, больше всего потребляющие CPU, через sys.dm_exec_query_stats.","tags":["cpu","load"]},{"metric":"Возраст последнего успешного бэкапа","warn":"> 24 часа (full)","crit":"> 48 часов (full)","tool":"msdb.dbo.backupset","note":"Возраст бэкапа определяет последнюю точку, к которой можно вернуться, и ваш RPO. В модели восстановления FULL восстановление на момент времени возможно с бэкапами журнала. Отслеживайте цепочку полных, разностных бэкапов и бэкапов журнала, чтобы гарантировать восстанавливаемость, и проводите регулярные тесты восстановления.","tags":["backup","recovery","rpo"]},{"metric":"Задержка дискового IO (avg read/write)","warn":"> 15 ms","crit":"> 30 ms","tool":"sys.dm_io_virtual_file_stats","note":"Средняя задержка чтения/записи по файлам показывает производительность хранилища. Для файлов данных ожидается около 15 ms, для файлов журнала — меньше. Устойчиво высокая задержка указывает на узкое место хранилища; выявите самые медленные файлы через sys.dm_io_virtual_file_stats.","tags":["io","disk","latency"]}]},"reference/alerts-mongodb.json":{"engineId":"mongodb","engine":"MongoDB","order":5,"alerts":[{"metric":"Доля грязных данных в кэше WiredTiger","warn":"> 5%","crit":"> 20%","tool":"db.serverStatus().wiredTiger.cache","note":"Доля грязного кэша — процент измененных страниц в кэше WiredTiger, еще не записанных на диск. Превышение 20% означает давление вытеснения и узкое место записи; MongoDB заставляет потоки приложения выполнять вытеснение. Оценивайте размер кэша и пропускную способность записи на диск вместе.","tags":["memory","cache","wiredtiger"]},{"metric":"Использование кэша WiredTiger (used %)","warn":"> 80%","crit":"> 95%","tool":"db.serverStatus().wiredTiger.cache","note":"Когда использование кэша превышает цель вытеснения 80%, WiredTiger начинает агрессивно вытеснять страницы и производительность падает. Если рабочий набор не помещается в кэш, растут физические чтения. Пересмотрите настройку wiredTigerCacheSizeGB и память сервера.","tags":["memory","cache","wiredtiger"]},{"metric":"Доступные тикеты (read/write tickets)","warn":"available/totalTickets < 20% или queueLength > 0 (устойчиво)","crit":"available = 0 и queueLength продолжает расти","tool":"db.serverStatus().wiredTiger.concurrentTransactions","note":"WiredTiger ограничивает одновременные операции чтения и записи системой тикетов. До 7.0 по умолчанию было фиксированно 128 тикетов; в 7.0+ число тикетов регулируется динамически (storageEngineConcurrencyAdjustmentAlgorithm: throughputProbing), поэтому отслеживайте соотношение available/totalTickets и queueLength, а не абсолютное число. Исчерпание тикетов и очередь операций — признак серьезного узкого места; обычно оно вызвано медленными запросами или насыщением дискового I/O.","tags":["concurrency","ticket","bottleneck"]},{"metric":"Отставание репликации","warn":"> 10 s","crit":"> 60 s","tool":"rs.printSecondaryReplicationInfo()","note":"Отставание репликации показывает в секундах, насколько secondary-член отстает от primary. Высокое отставание означает устаревшие данные в зависимости от read preference и риск потери данных при failover. Обычно вызвано узкими местами дискового I/O или сети на secondary либо долгими операциями.","tags":["replication","lag","replicaset"]},{"metric":"Использование подключений (connections %)","warn":"> 80%","crit":"> 90%","tool":"db.serverStatus().connections","note":"Приближение активных подключений к настроенному верхнему лимиту приводит к отклонению новых подключений. Поскольку каждое подключение расходует память и поток, большое их число также создает давление на ресурсы. Предотвращайте шторм подключений, правильно подбирая размер пула подключений на стороне приложения.","tags":["connection","limit","pool"]},{"metric":"Частота page fault","warn":"> 100 / s","crit":"> 1000 / s","tool":"db.serverStatus() / extra_info page_faults","note":"Page fault возникает, когда данных нет в памяти и их нужно считать с диска. Высокая частота показывает, что рабочий набор не помещается в память и система упирается в диск. Устойчиво высокие значения требуют увеличения памяти или оптимизации запросов/индексов.","tags":["memory","disk","pagefault"]},{"metric":"Операции в очереди","warn":"> 10","crit":"> 50","tool":"db.serverStatus().globalLock.currentQueue","note":"globalLock currentQueue показывает число чтений/записей, стоящих в очереди в ожидании блокировки или ресурса. Постоянно заполненная очередь указывает, что сервер не справляется с входящей нагрузкой. Изучайте ее вместе с исчерпанием тикетов и дисковым I/O, чтобы определить источник узкого места.","tags":["concurrency","queue","lock"]},{"metric":"Окно oplog","warn":"< 24 часа","crit":"< 6 часов","tool":"rs.printReplicationInfo()","note":"Окно oplog — промежуток времени, покрываемый oplog; определяет, как долго secondary может оставаться отключенным и при этом ресинхронизироваться. Если окно сужается, отставшему члену потребуется полная ресинхронизация. Окно сокращается с ростом нагрузки записи; соответственно увеличивайте размер oplog.","tags":["oplog","replication","recovery"]},{"metric":"Самый долгий запрос","warn":"> 30 s","crit":"> 120 s","tool":"db.currentOp() / slow query log","note":"Долгие запросы влияют на другие операции, расходуя ресурсы и удерживая блокировки. Обычно это запросы, выполняющие сканирование коллекции (COLLSCAN) из-за отсутствующего индекса. Находите медленные операции через профилировщик и currentOp и добавляйте подходящие индексы.","tags":["query","performance","index"]},{"metric":"Использование CPU","warn":"> 80%","crit":"> 90%","tool":"node_exporter / mongostat","note":"Устойчиво высокий CPU может быть вызван запросами без индексов, тяжелыми операциями агрегации или избыточными подключениями. Короткие пики нормальны, но устойчивое насыщение приводит к очередям операций. Используйте профилировщик, чтобы выявить и оптимизировать запросы, больше всего потребляющие CPU.","tags":["cpu","load"]},{"metric":"Использование диска (dbPath)","warn":"> 85%","crit":"> 95%","tool":"df / node_exporter","note":"Заполненный каталог данных лишает MongoDB возможности записи и может привести к повреждению. Хотя сжатие WiredTiger помогает, журнал и растущие коллекции могут быстро заполнить диск. Планируйте емкость и автоматически удаляйте ненужные данные с помощью TTL-индексов.","tags":["disk","capacity"]},{"metric":"Возраст последнего успешного бэкапа","warn":"> 24 часа","crit":"> 48 часов","tool":"mongodump / Ops Manager / запись snapshot","note":"Возраст бэкапа определяет последнюю точку, к которой можно вернуться, и ваш RPO. В сочетании с oplog становится возможным восстановление на момент времени. Делать бэкапы недостаточно; проверяйте их регулярными тестами восстановления и убедитесь, что в шардированных кластерах делаются согласованные снимки.","tags":["backup","recovery","rpo"]}]},"reference/alerts-redis.json":{"engineId":"redis","engine":"Redis","order":6,"alerts":[{"metric":"Использование памяти (used_memory vs maxmemory)","warn":"> 80%","crit":"> 95%","tool":"INFO memory / redis_exporter","note":"Отношение used_memory к maxmemory показывает, какая часть памяти Redis используется. При достижении лимита ключи вытесняются или запись отклоняется согласно maxmemory-policy. Отслеживайте рост памяти, чтобы предотвратить неожиданный OOM и волны вытеснения.","tags":["memory","maxmemory","capacity"]},{"metric":"Keyspace hit ratio","warn":"< 90%","crit":"< 80%","tool":"INFO stats (keyspace_hits/misses)","note":"Коэффициент попаданий — процент запрошенных ключей, найденных в кэше; вычисляется из keyspace_hits и keyspace_misses. Низкое значение указывает на неэффективное использование кэша, неверные TTL или холодный кэш. Для кэширующих нагрузок это значение должно быть как можно выше.","tags":["cache","hit","performance"]},{"metric":"Вытесненные ключи (evicted_keys)","warn":"> 1 / s","crit":"> 100 / s","tool":"INFO stats (evicted_keys)","note":"evicted_keys — число ключей, удаленных согласно maxmemory-policy из-за заполнения памяти. Постоянное вытеснение показывает, что памяти недостаточно и важные данные могут теряться. Сократите вытеснение, увеличив память или пересмотрев стратегию TTL либо модель данных.","tags":["memory","eviction","maxmemory"]},{"metric":"Заблокированные клиенты (blocked_clients)","warn":"> 50","crit":"> 200","tool":"INFO clients (blocked_clients)","note":"blocked_clients — число клиентов, ожидающих на блокирующих командах, таких как BLPOP/BRPOP/WAIT. Значения выше ожидаемых указывают, что потребители не успевают или растет отставание очереди. В нагрузках на основе очередей проверьте баланс производителей/потребителей.","tags":["client","blocking","queue"]},{"metric":"Коэффициент фрагментации памяти (mem_fragmentation_ratio)","warn":"> 1.5","crit":"> 2.0","tool":"INFO memory","note":"Коэффициент фрагментации — память, полученная от операционной системы, деленная на память, используемую Redis. Выше 1.5 указывает на избыточную фрагментацию; ниже 1.0 — на вытеснение памяти в своп, что очень опасно. Снизить фрагментацию можно, включив activedefrag или выполнив перезапуск.","tags":["memory","fragmentation"]},{"metric":"Статус связи с master (master_link_status)","warn":"down (< 10 s)","crit":"down (> 30 s)","tool":"INFO replication (master_link_status)","note":"master_link_status показывает состояние (up/down) подключения реплики к master. 'down' означает, что репликация нарушена и реплика отдает устаревшие данные. Долгие разрывы требуют полной ресинхронизации; немедленно проверьте сеть и состояние master.","tags":["replication","master","connection"]},{"metric":"Отставание репликации (разница master_repl_offset)","warn":"> 1 MB","crit":"> 10 MB","tool":"INFO replication (offset diff)","note":"Разница смещений master и реплики показывает в байтах, насколько реплика отстает. Большая разница означает риск потери данных при failover и несогласованность на читающих репликах. Изучите сетевую задержку и нагрузку реплики, чтобы убедиться, что разрыв сокращается.","tags":["replication","lag","offset"]},{"metric":"Статус последнего RDB bgsave (rdb_last_bgsave_status)","warn":"err (последняя попытка)","crit":"err (> 24 часа)","tool":"INFO persistence","note":"rdb_last_bgsave_status показывает, успешно ли (ok) завершился последний фоновый снимок. Неудачный bgsave означает, что персистентность не работает и при перезапуске есть риск потери данных. Обычно вызвано заполненным диском или нехваткой памяти для fork.","tags":["backup","persistence","rdb"]},{"metric":"Использование подключений (connected_clients)","warn":"> 80% maxclients","crit":"> 90% maxclients","tool":"INFO clients / maxclients","note":"Приближение connected_clients к лимиту maxclients приводит к отклонению новых подключений. Утечки подключений или неправильно настроенный пул могут вызывать резкие всплески. Правильно настройте пул подключений на стороне приложения и закрывайте простаивающие подключения.","tags":["connection","limit","client"]},{"metric":"Мгновенная задержка команд","warn":"> 1 ms","crit":"> 10 ms","tool":"redis-cli --latency / LATENCY DOCTOR","note":"Поскольку Redis однопоточный, задержка команд обычно в диапазоне микросекунд. Рост задержки может быть вызван медленными командами (KEYS, большой SORT), блокировкой во время fork или использованием свопа. Найдите источник через slowlog и команды LATENCY.","tags":["latency","performance","slowlog"]},{"metric":"Использование CPU","warn":"> 80% (одно ядро)","crit":"> 90% (одно ядро)","tool":"top / redis_exporter","note":"Поскольку основной поток Redis использует одно ядро, насыщение одного ядра замедляет весь сервер. Высокий CPU может быть вызван дорогими командами, слишком большим числом подключений или Lua-скриптами. Найдите дорогие команды через SLOWLOG и при необходимости распределите нагрузку по нескольким экземплярам.","tags":["cpu","load"]},{"metric":"Отклоненные подключения (rejected_connections)","warn":"> 0","crit":"> 10 / min","tool":"INFO stats (rejected_connections)","note":"rejected_connections — число подключений, отклоненных из-за достижения лимита maxclients. Значение выше нуля означает, что клиенты не обслуживаются и в приложении возникают ошибки. Немедленно исследуйте значение maxclients и утечки подключений.","tags":["connection","error","limit"]}]},"reference/alerts-elasticsearch.json":{"engineId":"elasticsearch","engine":"Elasticsearch","order":7,"alerts":[{"metric":"Использование heap JVM","warn":"> 75%","crit":"> 85%","tool":"_nodes/stats/jvm / elasticsearch_exporter","note":"Использование heap JVM показывает давление на память узла; устойчивые значения выше 75% означают частый GC и возможный OutOfMemory. Если heap превышает 85%, узел может перестать отвечать или выпасть из кластера. Задавайте heap примерно в половину памяти сервера и не более 30-31 GB.","tags":["memory","jvm","heap"]},{"metric":"Число неназначенных шардов (unassigned shards)","warn":">= 1 (реплика)","crit":">= 1 (первичный)","tool":"_cluster/health / _cat/shards","note":"Неназначенные шарды означают, что часть данных недоступна или не реплицирована. Неназначенный первичный шард означает риск потери данных; неназначенная реплика — потерю избыточности, и кластер становится yellow/red. Найдите причину через _cluster/allocation/explain.","tags":["shard","cluster","health"]},{"metric":"Использование disk watermark","warn":"> 85% (low)","crit":"> 90% (high) / > 95% (flood_stage)","tool":"_nodes/stats/fs / _cat/allocation","note":"Elasticsearch предпринимает поэтапные действия в зависимости от использования диска: на low watermark 85% перестает размещать новые шарды на узле, на high watermark 90% перемещает существующие шарды на другие узлы, а на flood_stage 95% ставит на индексы блокировку read_only_allow_delete. В 8.x на больших дисках значения max_headroom (low 200GB / high 150GB / flood 100GB свободно) имеют приоритет над процентами. В 7.4+ блокировка снимается автоматически, когда использование диска падает ниже high watermark; тем не менее освобождайте диск до превышения watermark.","tags":["disk","watermark","capacity"]},{"metric":"Отклоненные операции пула потоков (rejected)","warn":"> 0 (write/search)","crit":"> 100 / min","tool":"_cat/thread_pool / _nodes/stats/thread_pool","note":"Когда очередь пула потоков заполнена, входящие запросы отклоняются; отказ в пуле write означает потерю индексирования, в пуле search — неудачный запрос. Устойчивые отказы указывают, что узел не справляется с входящей нагрузкой. Сбалансируйте нагрузку, уменьшив размер bulk и конкурентность.","tags":["threadpool","rejected","load"]},{"metric":"Время паузы GC (old gen GC pause)","warn":"> 1 s","crit":"> 5 s","tool":"Журналы GC / _nodes/stats/jvm","note":"Во время долгих пауз GC узел не может отвечать и может потерять связь с master, что запускает перераспределение шардов. Частый и долгий GC старого поколения — симптом давления на heap. Пересмотрите размер heap и использование агрегаций и fielddata, требовательных к памяти.","tags":["jvm","gc","pause"]},{"metric":"Задержка индексирования","warn":"> 50 ms","crit":"> 200 ms","tool":"_nodes/stats/indices (index_time/index_total)","note":"Среднее время индексирования показывает производительность записи документов и вычисляется делением index_time_in_millis на index_total. Рост значений указывает на узкое место дискового IO, тяжелую цепочку анализа или давление refresh. Оптимизируйте размер bulk и настройку refresh_interval.","tags":["indexing","latency","performance"]},{"metric":"Задержка поиска","warn":"> 100 ms","crit":"> 500 ms","tool":"_nodes/stats/indices (query_time/query_total)","note":"Среднее время запроса напрямую отражает производительность поиска, которую видят пользователи, и вычисляется делением query_time_in_millis на query_total. Рост значений может быть вызван тяжелыми агрегациями, слишком большим числом шардов или холодным кэшем. Включите slow log, чтобы найти дорогие запросы.","tags":["search","latency","performance"]},{"metric":"Pending tasks","warn":"> 10","crit":"> 100","tool":"_cluster/pending_tasks","note":"Pending tasks — изменения cluster state, еще не примененные master-узлом (mapping, размещение шардов и т.д.). Постоянное накопление указывает, что master перегружен или cluster state стал слишком большим. Снизьте давление на master, сократив избыточное число шардов.","tags":["cluster","master","queue"]},{"metric":"Срабатывания circuit breaker","warn":"> 0 (tripped)","crit":"> 10 / min","tool":"_nodes/stats/breakers","note":"Circuit breaker отменяют запрос, чтобы операция не израсходовала избыточную память и не довела узел до OutOfMemory. Частые срабатывания указывают на давление на память из-за тяжелых агрегаций или больших запросов. Отслеживайте счетчик tripped и облегчайте проблемные запросы.","tags":["memory","breaker","jvm"]},{"metric":"Статус состояния кластера (cluster status)","warn":"yellow","crit":"red","tool":"_cluster/health","note":"Статус кластера может быть green (все шарды назначены), yellow (не хватает реплик) или red (не хватает первичных шардов, данные недоступны). Red требует немедленных действий, так как часть данных потеряна или недоступна. Yellow указывает на потерю избыточности, и его нельзя игнорировать.","tags":["cluster","health","shard"]},{"metric":"Использование CPU","warn":"> 80%","crit":"> 90%","tool":"_nodes/stats/os / node_exporter","note":"Устойчиво высокий CPU может быть вызван тяжелыми запросами, избыточным индексированием или операциями merge. Короткие пики нормальны, но устойчивое насыщение приводит к очередям запросов и индексирования. Выявите операции, больше всего потребляющие CPU, через hot threads API (_nodes/hot_threads).","tags":["cpu","load"]},{"metric":"Возраст последнего успешного snapshot","warn":"> 24 часа","crit":"> 48 часов","tool":"_snapshot/_status / SLM","note":"Возраст snapshot определяет последнюю точку, к которой можно вернуться, и ваш RPO. Автоматические регулярные бэкапы следует обеспечить через Snapshot Lifecycle Management (SLM). Если неудачные или пропущенные snapshot остаются незамеченными, при аварии данные восстановить нельзя; регулярно проверяйте статус snapshot.","tags":["backup","snapshot","rpo"]}]},"cloud/cloud-ops-1.json":{"category":"Подключение, бэкап и реплики","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":"Список экземпляров / баз данных","aliases":["list","instances","list all"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'","note":"Выводит идентификаторы всех экземпляров RDS. Добавьте --region, чтобы указать регион.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --query 'DBClusters[].DBClusterIdentifier'","note":"Выводит идентификаторы кластеров Aurora; члены кластера смотрите в поле DBClusterMembers.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances list","note":"Выводит все экземпляры Cloud SQL в проекте. Добавьте --project, чтобы указать проект.","danger":"safe"},"azure-sql":{"command":"az sql db list -g <rg> -s <server>","note":"Выводит базы данных в заданной группе ресурсов (-g) и логическом сервере (-s). Для списка серверов используйте 'az sql server list'.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters list","note":"Выводит все кластеры в проекте Atlas. Добавьте --projectId, чтобы указать проект.","danger":"safe"}}},{"id":"connect","title":"Подключение к экземпляру (найти endpoint + подключиться)","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":"Возвращает адрес подключения экземпляра (host). Порт возьмите из Endpoint.Port и подключитесь клиентом psql/mysql.","danger":"safe"},"aws-aurora":{"command":"aws rds describe-db-clusters --db-cluster-identifier <id> --query 'DBClusters[0].Endpoint'","note":"Возвращает writer endpoint кластера. Для доступа только на чтение используйте поле ReaderEndpoint.","danger":"safe"},"gcp-cloudsql":{"command":"gcloud sql instances describe <instance> --format='value(ipAddresses[].ipAddress)'","note":"Возвращает IP-адрес(а) экземпляра. Для защищенных подключений рекомендуется Cloud SQL Auth Proxy.","danger":"safe"},"azure-sql":{"command":"az sql db show-connection-string -s <server> -n <db> -c ado.net","note":"Генерирует строку подключения для заданного типа клиента (-c: ado.net, jdbc, odbc, php и т.д.); пользователя/пароль подставляете вы.","danger":"safe"},"mongodb-atlas":{"command":"atlas clusters connectionStrings describe <clusterName>","note":"Возвращает стандартную и SRV-строки подключения кластера. Нужны запись в IP access list и пользователь базы данных.","danger":"safe"}}},{"id":"take-snapshot","title":"Ручной бэкап / snapshot","aliases":["snapshot","backup","backup"],"providers":{"aws-rds":{"command":"aws rds create-db-snapshot --db-instance-identifier <id> --db-snapshot-identifier <snap-id>","note":"Создает ручной snapshot экземпляра (хранится до ручного удаления). Возможна кратковременная приостановка I/O.","danger":"caution"},"aws-aurora":{"command":"aws rds create-db-cluster-snapshot --db-cluster-identifier <id> --db-cluster-snapshot-identifier <snap-id>","note":"Создает ручной snapshot кластера Aurora. Поскольку бэкапы Aurora непрерывны, это фиксированная копия.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql backups create --instance=<instance>","note":"Создает бэкап экземпляра по запросу. Во время операции возможно кратковременное влияние на производительность.","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 резервируется автоматически; для переносимого ручного бэкапа эта команда экспортирует базу данных в BACPAC. Восстановление на момент времени — отдельная возможность.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups snapshots create <clusterName>","note":"Запускает snapshot-бэкап кластера по запросу. Должен быть включен Cloud Backup.","danger":"caution"}}},{"id":"restore-pitr","title":"Восстановление на момент времени (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":"Создает НОВЫЙ экземпляр из исходного на заданный момент времени; существующий экземпляр не перезаписывается. Используйте --use-latest-restorable-time, чтобы восстановить на последнюю точку.","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":"Восстанавливает кластер Aurora на заданный момент времени как новый кластер. Затем нужно добавить экземпляры через create-db-instance.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances clone <src> <new> --point-in-time '2026-07-03T10:00:00Z'","note":"Создает клон исходного экземпляра на заданный момент времени как новый экземпляр. На источнике должно быть включено журналирование binary/WAL.","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":"Восстанавливает базу данных на заданный момент времени как НОВУЮ базу; исходная база сохраняется. Время указывается в UTC.","danger":"caution"},"mongodb-atlas":{"command":"atlas backups restores start pointInTime --clusterName <cluster> --pointInTimeUTCMillis <ms> --targetClusterName <target> --targetProjectId <projId>","note":"Запускает восстановление на заданное время UTC с помощью Continuous Cloud Backup. Целевой кластер перезаписывается; действуйте осторожно.","danger":"danger"}}},{"id":"read-replica","title":"Создание реплики для чтения","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":"Создает асинхронную реплику для чтения из исходного экземпляра. Для межрегиональной копии используйте --source-region.","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":"В Aurora reader — это экземпляр, добавленный в кластер; он использует общее хранилище кластера. Задайте значение engine в соответствии с кластером (aurora-mysql / aurora-postgresql).","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <replica> --master-instance-name=<src> --region=<region>","note":"Создает реплику для чтения из основного (master) экземпляра. На источнике должно быть включено binary logging.","danger":"caution"},"azure-sql":{"command":"az sql db replica create -g <rg> -s <server> -n <db> --partner-server <target-server>","note":"Создает активную геореплицированную читаемую вторичную копию базы данных на другом сервере. Целевой сервер должен существовать.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <clusterName> --file cluster.json","note":"Кластеры Atlas уже являются реплицированными replica set. Для дополнительной емкости чтения добавьте узлы readOnly или analytics в replicationSpecs и выполните 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":"На экземпляре Multi-AZ выполняет принудительную перезагрузку, запускающую failover на standby. Ожидайте кратковременный простой.","danger":"danger"},"aws-aurora":{"command":"aws rds failover-db-cluster --db-cluster-identifier <cluster> --target-db-instance-identifier <reader>","note":"Повышает reader кластера Aurora до нового writer. Если цель не указана, выбирается подходящая реплика; ожидайте кратковременный простой.","danger":"danger"},"gcp-cloudsql":{"command":"gcloud sql instances failover <instance>","note":"Выполняет failover HA-экземпляра (высокая доступность) в standby-зону. Работает только на экземплярах с включенной HA; возникает простой.","danger":"danger"},"azure-sql":{"command":"az sql failover-group set-primary -g <rg> -s <secondary-server> -n <fog-name>","note":"Переносит роль primary на вторичный сервер в auto-failover group (плановый failover). Можно принудительно выполнить с --allow-data-loss, что несет риск потери данных.","danger":"danger"},"mongodb-atlas":{"command":"atlas clusters failover <clusterName>","note":"Atlas выполняет failover автоматически; эта команда запускает ручной failover для тестирования. Primary-узел меняется, и приложение должно переподключиться.","danger":"danger"}}}]},"cloud/cloud-ops-2.json":{"category":"Конфигурация, масштабирование и мониторинг","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":"Изменение размера экземпляра (вертикальное масштабирование)","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 применяет изменение сразу и перезапускает экземпляр (кратковременный простой). Без этого флага изменение применяется в следующее окно обслуживания. На экземплярах Multi-AZ простой сокращается примерно до времени failover.","danger":"caution"},"aws-aurora":{"command":"aws rds modify-db-instance --db-instance-identifier <writer-or-reader-id> --db-instance-class db.r6g.2xlarge --apply-immediately","note":"В Aurora каждый экземпляр (writer и readers) масштабируется отдельно; меняется размер экземпляра, а не кластера. Сначала масштабируйте readers, затем выполните failover для замены writer, чтобы минимизировать простой. В Serverless v2 масштабирование задается через min/max ACU.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --tier db-custom-4-16384","note":"Пользовательский тип машины задается как db-custom-CPU-MEMORY (пример: 4 vCPU, 16384 MB RAM). Изменение перезапускает экземпляр, вызывая кратковременный простой. Можно также использовать предопределенные уровни (например, db-n1-standard-4).","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --service-objective S3","note":"В модели DTU используйте service objective вроде S3/P2; в модели vCore — --edition GeneralPurpose --family Gen5 --capacity 4. Масштабирование выполняется онлайн; ближе к концу операции возможен кратковременный обрыв подключения на несколько секунд.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --tier M30","note":"Atlas изменяет размер узлов replica set поочередно (rolling), поэтому простоя нет. Требуется флаг --projectId или проект по умолчанию. При понижении следите за лимитами диска и памяти.","danger":"caution"}}},{"id":"parameter-group","title":"Изменение параметров (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":"Используйте ApplyMethod=immediate для динамических параметров и pending-reboot для статических. Parameter group должна быть привязана к экземпляру; изменения статических параметров вступают в силу после перезагрузки. Для создания группы используйте create-db-parameter-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":"В Aurora параметры уровня кластера управляются через DB cluster parameter group, а параметры отдельных экземпляров — через DB parameter group (modify-db-parameter-group). Статические параметры требуют перезагрузки всего кластера.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --database-flags max_connections=500,log_min_duration_statement=1000","note":"--database-flags задает ВСЕ флаги сразу; ранее заданные флаги, отсутствующие в списке, сбрасываются, поэтому указывайте их все вместе. Некоторые флаги требуют перезапуска экземпляра. Текущие флаги смотрите через '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":"В Azure SQL вместо классической серверной конфигурации настройки в основном применяются через T-SQL: ALTER DATABASE SCOPED CONFIGURATION ...; sp_configure на уровне сервера ограничен. Для параметров elastic pool/базы данных используйте флаги 'az sql db update'.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters advancedSettings update <name> --oplogMinRetentionHours 24 --oplogSizeMB 20480","note":"В Atlas низкоуровневые аргументы процесса MongoDB можно задать лишь в ограниченной мере через 'advancedSettings' (oplog, полнотекстовый поиск, глубина индексов и т.д.). Большинство серверных параметров управляется Atlas и напрямую не меняется.","danger":"caution"}}},{"id":"monitoring","title":"Просмотр метрик и производительности","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":"Базовые метрики (CPU, подключения, IOPS, задержка) находятся в пространстве имен CloudWatch AWS/RDS. Для анализа на уровне запросов включите Performance Insights: 'aws pi get-resource-metrics --service-type RDS ...' или используйте панель Performance Insights в консоли.","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 (AuroraReplicaLag, VolumeReadIOPs, CommitLatency, buffer cache hit), также находятся в пространстве имен AWS/RDS. Для Aurora рекомендуется Performance Insights, который дает анализ top SQL и событий ожидания.","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 находятся в Cloud Monitoring в cloudsql.googleapis.com/... Для анализа на уровне запросов включите Cloud SQL Query Insights (консоль или API); он показывает самые медленные запросы и события ожидания.","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":"Метрики вроде cpu_percent, dtu_consumption_percent, storage_percent и connection_successful поступают из Azure Monitor. Для производительности запросов используйте панели Query Performance Insight и Intelligent Insights (Azure SQL Analytics).","danger":"safe"},"mongodb-atlas":{"command":"atlas metrics processes <host>:<port> --granularity PT5M --period P1D --type SYSTEM_CPU_USER,QUERY_TARGETING_SCANNED_OBJECTS_PER_RETURNED","note":"Измерения на уровне процессов получают через 'atlas metrics processes', дисковые — через 'atlas metrics disks'. Для рекомендаций по медленным запросам используйте Performance Advisor: 'atlas performanceAdvisor slowQueryLogs list' и 'atlas performanceAdvisor suggestedIndexes list'.","danger":"safe"}}},{"id":"maintenance-window","title":"Окно обслуживания и управление обновлениями","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":"Окно задается в UTC в формате ddd:hh24:mi-ddd:hh24:mi и должно быть не менее 30 минут. Ожидающее обслуживание выводится через 'aws rds describe-pending-maintenance-actions' и применяется сразу или в следующее окно через 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":"В Aurora окно обслуживания уровня кластера задается через modify-db-cluster, а уровня экземпляра — через modify-db-instance. Ожидающие обновления движка/ОС управляются через apply-pending-maintenance-action; обновление версии движка может потребовать простоя.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances patch <id> --maintenance-window-day SUN --maintenance-window-hour 3 --maintenance-release-channel production","note":"Задает день (MON..SUN), час (0-23, локальное время экземпляра/UTC) и канал выпуска (production или preview). Флагами --deny-maintenance-period-* можно также задать период, в течение которого обслуживание не выполняется.","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":"Окно обслуживания Azure SQL управляется назначением предопределенной Maintenance Configuration (вечерние окна будних/выходных дней вместо окна по умолчанию). Список конфигураций выводится через '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=воскресенье ... 7=суббота, hourOfDay 0-23 (UTC). Используйте 'atlas maintenanceWindows defer', чтобы отложить ожидающее обслуживание на неделю, и 'atlas maintenanceWindows start', чтобы запустить его сразу.","danger":"caution"}}},{"id":"encryption","title":"Просмотр/настройка шифрования (at-rest)","aliases":["encryption","encryption at rest","at-rest","kms","tde","cmek"],"providers":{"aws-rds":{"command":"aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].[StorageEncrypted,KmsKeyId]'   # at creation: aws rds create-db-instance ... --storage-encrypted --kms-key-id <kms-arn>","note":"Шифрование at-rest в RDS можно включить только ПРИ СОЗДАНИИ экземпляра; чтобы включить его на существующем незашифрованном экземпляре, создайте зашифрованную копию snapshot и восстановитесь из нее. Ключом управляет 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":"В Aurora шифрование задается на уровне кластера и только при создании. Чтобы зашифровать существующий незашифрованный кластер, скопируйте snapshot как зашифрованный и восстановите его. Все экземпляры и бэкапы используют один и тот же ключ KMS.","danger":"caution"},"gcp-cloudsql":{"command":"gcloud sql instances create <id> --disk-encryption-key projects/<project>/locations/<region>/keyRings/<ring>/cryptoKeys/<key>   # to view: gcloud sql instances describe <id> --format='value(diskEncryptionConfiguration.kmsKeyName)'","note":"Cloud SQL по умолчанию шифруется ключом, управляемым Google; ключ, управляемый клиентом (CMEK), можно назначить только при создании экземпляра через --disk-encryption-key. Учетной записи службы Cloud SQL нужно выдать права encrypt/decrypt на ключ.","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":"В Azure SQL шифрование at-rest обеспечивается Transparent Data Encryption (TDE) и по умолчанию включено на новых базах данных. Чтобы использовать собственный ключ, настройте 'az sql server tde-key set' (Azure Key Vault / BYOK) на уровне сервера.","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":"В Atlas данные по умолчанию шифруются at-rest. Дополнительно envelope encryption с ключом клиента (BYOK) через AWS KMS / Azure Key Vault / GCP KMS включается для всего проекта через 'atlas encryptionAtRest' и применяется к кластеру.","danger":"caution"}}},{"id":"storage-autoscale","title":"Расширение хранилища / включение автомасштабирования","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 задает текущий размер; --max-allocated-storage задает верхний предел автомасштабирования хранилища. Расширение хранилища выполняется онлайн; однако между изменениями хранилища нужно ждать не менее 6 часов, и уменьшить хранилище нельзя.","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 масштабируется полностью автоматически (шагами по 10 GB, до 128 TB в зависимости от движка) и растет по мере использования; --allocated-storage / --max-allocated-storage не задаются. Вы только отслеживаете использование; управлять размером не нужно.","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 включает автоматический рост при приближении диска к заполнению; --storage-auto-increase-limit задает верхний предел (0 = без ограничения). Хранилище можно только увеличить, УМЕНЬШИТЬ ЕГО НЕЛЬЗЯ; тщательно выбирайте начальный размер и предел.","danger":"caution"},"azure-sql":{"command":"az sql db update -g <rg> -s <server> -n <db> --max-size 500GB","note":"В Azure SQL максимальный размер базы данных задается через --max-size; база растет автоматически в пределах выделенного пространства. Максимальный размер зависит от выбранного уровня обслуживания/лимита vCore. На уровне Hyperscale хранилище масштабируется автоматически и почти без ограничений.","danger":"caution"},"mongodb-atlas":{"command":"atlas clusters update <name> --diskSizeGB 200   # for autoscaling, diskGBEnabled=true via extended configuration (advancedSettings / API)","note":"Размер диска увеличивается через --diskSizeGB. В Atlas автомасштабирование хранилища (Auto-scale storage) по умолчанию включено на кластерах M10 и выше; диск растет автоматически при заполнении на 90%. Включение/отключение управляется в конфигурации кластера (autoScaling.diskGBEnabled).","danger":"caution"}}}]},"guide/best-practices.json":{"topics":[{"id":"indexing-strategy","title":"Стратегия индексирования","category":"Производительность","summary":"Индексы ускоряют читающие запросы, но добавляют стоимость записи и хранения; добавляйте их по необходимости и на основе измерений.","points":["Ориентируйтесь на столбцы WHERE, JOIN и ORDER BY; в составном индексе ставьте первым самый селективный столбец (правило левого префикса).","Используйте покрывающие индексы: включайте в индекс все столбцы, нужные запросу, чтобы исключить обращение к таблице (lookup).","Выявляйте и удаляйте неиспользуемые и избыточные индексы (дубликаты или префиксы другого индекса); каждый индекс замедляет INSERT/UPDATE/DELETE.","Избегайте отдельных B-tree индексов на столбцах с низкой кардинальностью (например, пол, boolean); при необходимости используйте частичный/фильтрованный индекс.","Проверяйте через EXPLAIN/EXPLAIN ANALYZE, что индекс действительно используется; функция или преобразование типа отключают индекс (как в WHERE UPPER(name))."],"level":"junior"},{"id":"normalization","title":"Нормализация и денормализация","category":"Проектирование","summary":"Нормализация обеспечивает целостность данных и отсутствие избыточности; денормализация — осознанный компромисс ради производительности чтения.","points":["В OLTP-системах по умолчанию стремитесь к 3NF; предотвращайте избыточность данных и аномалии обновления.","Денормализуйте только при измеренном узком месте производительности и обосновывайте это.","Обеспечивайте согласованность денормализованных данных триггерами, материализованными представлениями или на уровне приложения.","Для отчетных/аналитических нагрузок рассмотрите отдельную денормализованную схему (star schema) или реплику для чтения.","Преждевременная денормализация — технический долг; сначала попробуйте решить проблему правильным индексом и запросом."],"level":"senior"},{"id":"data-types","title":"Выбор правильного типа данных","category":"Проектирование","summary":"Правильный и минимально возможный тип данных напрямую улучшает хранение, размер индексов и производительность.","points":["Выбирайте наименьший тип, отвечающий потребности: SMALLINT для возраста, DECIMAL/NUMERIC для денег (никогда FLOAT).","Всегда храните время с часовым поясом (TIMESTAMPTZ / UTC); избегайте хранения локального времени.","Для фиксированных ограниченных наборов значений используйте ENUM или справочную таблицу; ограничивайте значения вместо свободного текста.","Для текста переменной длины задавайте размер по реальной потребности или используйте TEXT вместо излишне большого VARCHAR(255).","Если используете UUID как PK, следите за фрагментацией индекса; рассмотрите последовательные UUID (v7) или BIGINT identity.","Выбирайте семантику NULL осознанно; явно отражайте в проекте разницу между 'неизвестно' и 'пусто'."],"level":"junior"},{"id":"naming-conventions","title":"Соглашения об именовании","category":"Проектирование","summary":"Единообразное именование облегчает читаемость, сопровождение и общение в команде; задайте стандарт с самого начала.","points":["Выберите одно соглашение и применяйте его везде: snake_case распространен и устойчив к проблемам чувствительности к регистру.","Определитесь с единственным или множественным числом в именах таблиц; предпочитайте осмысленные имена столбцов без сокращений.","Называйте внешние ключи так, чтобы источник был очевиден (например, customer_id), и давайте индексам/ограничениям стандартные префиксы (fk_, ix_, uq_).","Не используйте зарезервированные слова (order, user, group) как имена таблиц/столбцов.","Делайте столбцы boolean явными с префиксами is_/has_; добавляйте к столбцам дат единообразные суффиксы, такие как _at, _date."],"level":"junior"},{"id":"partitioning-strategy","title":"Стратегия секционирования","category":"Производительность","summary":"Секционирование делит большие таблицы на управляемые части; улучшает обслуживание, очистку и производительность запросов.","points":["Выбирайте ключ секционирования по шаблонам запросов и архивации; для временных рядов чаще всего используется RANGE-секционирование по дате.","Проверяйте, что partition pruning работает: запросы должны содержать ключ секционирования в WHERE, иначе сканируются все секции.","Вместо удаления старых данных очищайте их мгновенно и почти без блокировок через DROP/DETACH секции.","Избыточное число секций добавляет затраты на планирование и метаданные; выбирайте разумную гранулярность (месяц/неделя).","Настройте задание обслуживания, автоматически создающее будущие секции; предотвращайте ошибки записи при отсутствии подходящей секции."],"level":"senior"},{"id":"connection-pooling","title":"Пул подключений","category":"Эксплуатация","summary":"Пул подключений устраняет высокую стоимость установки подключений и защищает базу данных от лавины подключений.","points":["Не открывайте новое подключение на каждый запрос; используйте пул в приложении или на промежуточном уровне (PgBouncer, HikariCP).","Подбирайте размер пула по ядрам CPU и нагрузке; слишком большой пул душит базу данных (небольшой пул обычно лучше).","Осознанно задавайте время жизни подключения, тайм-ауты простоя и получения; включите обнаружение утечек.","Планируйте пул с учетом лимита max_connections базы данных; учитывайте сумму по всем экземплярам приложения.","Используйте короткие транзакции, чтобы подключения быстро возвращались в пул; не ждите внешний I/O внутри транзакции."],"level":"senior"},{"id":"transaction-design","title":"Проектирование транзакций (короткие и атомарные)","category":"Производительность","summary":"Транзакции должны быть короткими, атомарными и охватывать одну логическую единицу работы; это минимизирует длительность блокировок.","points":["Делайте транзакции как можно короче; не заключайте в них взаимодействие с пользователем, вызовы внешних API или долгие вычисления.","Объединяйте в одну транзакцию только операции, которые должны вместе завершиться успехом или неудачей.","Всегда обновляйте строки в одном и том же порядке, чтобы сократить конфликты блокировок (основа предотвращения взаимоблокировок).","Избегайте излишне широких блокировок; не помещайте операции только для чтения в лишние блоки транзакций.","Определите четкий путь отката при ошибке; не оставляйте частичных commit или открытых транзакций.","Фиксируйте долгие массовые операции пакетами; одна гигантская транзакция вызывает раздувание WAL/undo."],"level":"senior"},{"id":"isolation-levels","title":"Выбор уровня изоляции","category":"Производительность","summary":"Уровень изоляции задает баланс между согласованностью и конкурентностью; выбирайте его осознанно исходя из бизнес-правил.","points":["Знайте типы аномалий: dirty read, non-repeatable read, phantom read; понимайте, какой уровень что предотвращает.","Read Committed достаточно для большинства OLTP; повышайте уровень только при реальной необходимости.","Для критичных операций с деньгами/запасами используйте Serializable или явные блокировки (SELECT ... FOR UPDATE).","При Serializable добавьте логику автоматического повтора при ошибках сериализации.","Знайте разницу в поведении между MVCC и движками на основе блокировок; один и тот же уровень может давать разные гарантии на разных движках."],"level":"expert"},{"id":"deadlock-prevention","title":"Предотвращение взаимоблокировок","category":"Производительность","summary":"Взаимоблокировка возникает, когда транзакции ждут ресурсов друг друга; во многом ее можно предотвратить проектированием.","points":["Всегда обращайтесь к ресурсам/строкам в единообразном одинаковом порядке; различия в порядке — самый частый источник взаимоблокировок.","Делайте транзакции короткими и сокращайте число блокировок; избегайте излишне широких обновлений.","При необходимости делайте конфликты предсказуемыми ранней блокировкой (SELECT ... FOR UPDATE).","Перехватывайте ошибки взаимоблокировки в приложении и автоматически повторяйте транзакцию; полностью исключить взаимоблокировки часто невозможно.","Регулярно просматривайте журналы/графы взаимоблокировок; устраняйте повторяющиеся шаблоны изменением индексов или порядка доступа."],"level":"expert"},{"id":"sharding-strategy","title":"Стратегия шардинга / горизонтального масштабирования","category":"Проектирование","summary":"Шардинг преодолевает ограничения одного сервера, распределяя данные по нескольким узлам; поскольку он добавляет серьезную сложность, это должно быть крайней мерой.","points":["Сначала исчерпайте вертикальное масштабирование, индексирование, кэширование и реплики для чтения; шардируйте только при реальной необходимости.","Тщательно выбирайте ключ шардинга: он должен давать мало межшардовых запросов и равномерное распределение без горячих точек.","Используйте consistent hashing или виртуальные шарды для перебалансировки (resharding); планируйте рост с самого начала.","Межшардовые JOIN и распределенные транзакции дороги; проектируйте схему так, чтобы минимизировать их.","Решите вопрос генерации глобально уникальных ID (последовательные ID в стиле snowflake и т.д.) и уровня маршрутизации по шардам."],"level":"expert"},{"id":"backup-strategy","title":"Стратегия бэкапа (3-2-1)","category":"Бэкап и HA","summary":"Надежная стратегия бэкапа — последняя линия защиты от аварий; бэкап не считается, пока он не протестирован.","points":["Применяйте правило 3-2-1: 3 копии, 2 разных носителя, 1 копия вне площадки (offsite/другой регион).","Сочетайте полные и инкрементальные бэкапы с архивацией WAL/binlog, чтобы получить восстановление на момент времени (PITR).","Регулярно тестируйте бэкапы, РЕАЛЬНО восстанавливая их; непротестированный бэкап — не бэкап.","Шифруйте бэкапы и при хранении, и при передаче; ограничьте доступ и задайте срок хранения политикой.","Храните неизменяемую (immutable) или изолированную (air-gapped) копию для защиты от программ-вымогателей."],"level":"junior"},{"id":"hadr-design","title":"Проектирование HA/DR и RPO/RTO","category":"Бэкап и HA","summary":"Проектирование высокой доступности и аварийного восстановления начинается с определения бизнес-целей в виде метрик RPO и RTO.","points":["Определите цели RPO (допустимая потеря данных) и RTO (допустимое время простоя) вместе с бизнесом.","Синхронная репликация дает RPO, близкий к нулю, но добавляет задержку; асинхронная производительна, но несет риск потери данных.","Настройте автоматический failover и проверяйте его работоспособность регулярными учениями по failover.","Используйте механизм quorum/witness для предотвращения split-brain; никогда не допускайте, чтобы оба узла стали master.","Размещайте DR-среду в другом физическом регионе; будьте устойчивы к отказу одного региона/центра обработки данных."],"level":"expert"},{"id":"capacity-planning","title":"Планирование емкости и роста","category":"Эксплуатация","summary":"Планирование емкости предотвращает неожиданные простои, обеспечивая ресурсы с опережением тренда роста.","points":["Измеряйте во времени скорость роста данных, IOPS, CPU, память и использование подключений и выводите тренды.","Задайте ранние пороговые оповещения по использованию диска (например, 75%/85%); действуйте до того, как закончится место.","Планируйте рост на основе прогнозов; учитывайте пиковую нагрузку и сезонные колебания.","Регулярно пересматривайте число подключений, bloat таблиц/индексов и потребности в архивации.","Определите путь масштабирования (вертикальное, реплики, секционирование/шардинг) до достижения узкого места."],"level":"senior"},{"id":"migration-strategy","title":"Стратегия миграции (минимальный простой)","category":"Эксплуатация","summary":"Миграции схемы и данных должны следовать обратимому плану, минимизирующему простой, по возможности онлайн.","points":["Проектируйте каждую миграцию обратимой и идемпотентной; никогда не выходите в продуктив без плана отката.","Избегайте блокирующего DDL на больших таблицах; используйте инструменты онлайн-изменения схемы (PG: pg-osc/pgroll; MySQL: gh-ost, pt-osc).","Применяйте шаблон expand-contract для обратно/прямо совместимых изменений: сначала добавьте, разверните, затем удалите старое.","Переносите большие объемы данных малыми пакетами; не создавайте блокировки и раздувание WAL одним гигантским UPDATE.","Сначала отрепетируйте миграцию в среде, близкой к продуктиву, с реалистичным объемом данных и измерьте ее длительность.","Заранее зафиксируйте в письменном плане момент переключения, шаги проверки и решение об откате."],"level":"senior"},{"id":"schema-versioning","title":"Версионирование схемы (Flyway/Liquibase)","category":"Эксплуатация","summary":"Изменения схемы следует вести миграциями, которые хранятся вместе с кодом, версионируются и применяются автоматически; ручное вмешательство чревато ошибками.","points":["Версионируйте все изменения схемы инструментами вроде Flyway/Liquibase и храните их в системе контроля версий.","Не выполняйте DDL вручную в продуктиве; каждое изменение должно проходить через проверенный файл миграции.","Держите миграции упорядоченными и уникальными; никогда не изменяйте примененный файл миграции, исправляйте новой миграцией.","Применяйте и проверяйте миграции автоматически в конвейере CI/CD; гарантируйте согласованность схемы между средами.","Продвигайте код приложения и версию схемы синхронно; используйте шаблон expand-contract для обратно совместимых переходов."],"level":"senior"},{"id":"least-privilege","title":"Принцип наименьших привилегий","category":"Безопасность","summary":"У каждого пользователя и приложения должны быть только привилегии, необходимые для работы; избыточные привилегии расширяют поверхность атаки.","points":["Используйте для приложения отдельные учетные записи с ограниченными привилегиями; никогда не подключайтесь как superuser/admin.","Выдавайте привилегии ролям, а не напрямую пользователям; управляйте доступом через членство в ролях.","Разрешайте только нужные таблицы и операции (SELECT/INSERT/UPDATE); избегайте широкого GRANT ALL.","Периодически пересматривайте привилегии и отзывайте неиспользуемые; немедленно удаляйте доступ при уходе сотрудника/сервиса.","Не встраивайте секреты (пароли, строки подключения) в код; используйте менеджер секретов (vault) и регулярно их ротируйте."],"level":"junior"},{"id":"encryption-basics","title":"Основы шифрования (at-rest/in-transit)","category":"Безопасность","summary":"Данные следует шифровать и при хранении, и при передаче; шифрование — базовая защита для соответствия требованиям и от утечек данных.","points":["Требуйте TLS при передаче; отклоняйте незашифрованные подключения и включите проверку сертификатов.","Применяйте шифрование at rest (TDE или шифрование диска/тома), чтобы украденный диск/бэкап нельзя было прочитать.","Серьезно относитесь к управлению ключами: храните ключи в отдельном KMS/HSM и регулярно их ротируйте.","Дополнительно шифруйте на уровне приложения особо чувствительные поля, такие как номера кредитных карт и национальные ID (field-level).","Шифруйте и бэкапы; распространяйте защиту продуктива на бэкапы и реплики.","Маскируйте или анонимизируйте чувствительные данные в тестовых средах и средах разработки (data masking)."],"level":"senior"},{"id":"monitoring-basics","title":"Основы мониторинга (golden signals)","category":"Эксплуатация","summary":"Эффективный мониторинг — это сбор правильных метрик и оповещения по значимым порогам; он выявляет проблемы до того, как их заметят пользователи.","points":["Отслеживайте golden signals: задержку, трафик, частоту ошибок и насыщение.","Добавьте метрики, специфичные для базы данных: медленные запросы, время блокировок/ожиданий, число подключений, коэффициент попаданий в кэш, отставание репликации.","Привязывайте оповещения к влиянию и порогам, по которым можно действовать, а не к симптомам; шумные оповещения вызывают усталость от оповещений.","Держите журнал медленных запросов и статистику запросов (например, pg_stat_statements) постоянно включенными и регулярно их просматривайте.","Храните историю метрик; постройте базовую линию для трендов и планирования емкости.","Всегда настраивайте оповещения о критичных условиях, таких как остаток места на диске и успешность бэкапов."],"level":"junior"},{"id":"query-design","title":"Проектирование запросов и предотвращение N+1","category":"Производительность","summary":"Эффективный запрос получает только нужные данные с минимумом обращений; N+1 — самая частая и самая коварная ошибка производительности.","points":["Выбирайте только нужные столбцы вместо SELECT *; это улучшает использование сети и памяти и возможности покрывающих индексов.","Предотвращайте проблему N+1: используйте JOIN или пакетный (IN) запрос вместо одиночных запросов в цикле; включите eager loading в ORM.","Разбивайте большие наборы результатов на страницы; на больших данных предпочитайте keyset (seek) пагинацию вместо OFFSET.","Оставляйте бизнес-логику запросу насколько возможно; не выбирайте всю таблицу, чтобы фильтровать/агрегировать в приложении.","Изучайте фактический план и оценки строк через EXPLAIN ANALYZE; устраняйте полные сканирования таблиц и ошибки оценки.","Поддерживайте частые дорогие запросы агрегации материализованным представлением или заранее вычисленной сводной таблицей."],"level":"junior"},{"id":"large-table-management","title":"Управление большими таблицами (архивация/секционирование)","category":"Производительность","summary":"Постоянно растущие таблицы со временем замедляют запросы, обслуживание и бэкапы; держите их под контролем архивацией и секционированием.","points":["Разделяйте горячие (активные) и холодные (исторические) данные; переносите старые данные в архивную таблицу или дешевое хранилище.","Секционируйте таблицу по дате/диапазону; очищайте старые данные мгновенно через DROP секции вместо DELETE.","Выполняйте большие удаления/обновления малыми пакетами; один гигантский DELETE создает блокировки, раздувание WAL и bloat таблицы.","Регулярно отслеживайте bloat и размер таблиц/индексов; при необходимости применяйте онлайн-реорганизацию (VACUUM/reorg).","Держите индексы, соответствующие частым запросам; поддержка лишних индексов на большой таблице обходится очень дорого."],"level":"senior"},{"id":"caching-strategy","title":"Стратегия кэширования","category":"Производительность","summary":"Кэш снижает задержку и нагрузку, перенося повторяющиеся чтения из базы данных; настоящая сложность — инвалидация.","points":["Используйте кэш для дорогих, часто читаемых и редко меняющихся данных; не пытайтесь кэшировать все.","Выберите стратегию инвалидации: TTL, обновление при записи (write-through) или удаление при записи (write-invalidate).","Определите допустимую устарелость данных исходя из бизнес-правил; не полагайтесь на кэш там, где нужна строгая согласованность.","Предотвращайте cache stampede (множество запросов одновременно заполняют пустой кэш) блокировкой или объединением запросов.","Отслеживайте коэффициент попаданий в кэш; низкий коэффициент показывает, что кэш не приносит пользы.","Кэш — уровень ускорения, а не надежное состояние; при потере кэша система должна продолжать корректно работать."],"level":"senior"},{"id":"statistics-maintenance","title":"Дисциплина статистики и обслуживания","category":"Эксплуатация","summary":"Актуальная статистика и регулярное обслуживание обеспечивают правильные решения планировщика запросов и не дают производительности деградировать со временем.","points":["Поддерживайте статистику актуальной (ANALYZE / auto-analyze); устаревшая статистика подталкивает планировщик к неверному плану.","Регулярно выполняйте VACUUM/очистку для удаления мертвых строк и bloat; на движках с MVCC это критично.","Отслеживайте фрагментацию индексов и при необходимости перестраивайте их онлайн (REINDEX/rebuild).","После массовой загрузки обновляйте статистику вручную; не давайте планировщику работать со старым распределением.","Планируйте задания обслуживания на окна с низким трафиком, отслеживайте их длительность/влияние и автоматизируйте их.","Следите за регрессиями планов (внезапно замедлившийся запрос); актуальность статистики — первое, что нужно проверить."],"level":"senior"}]},"guide/best-practices-2.json":{"topics":[{"id":"iac","title":"Управление инфраструктурой как кодом (IaC)","category":"Эксплуатация","summary":"Серверы баз данных, сеть и конфигурация должны описываться версионируемым кодом, а не вручную, чтобы среды были воспроизводимыми, проверяемыми и обратимыми.","points":["Храните определения серверов, parameter group, сети и пользователей как код с помощью инструментов вроде Terraform/Pulumi/CloudFormation в системе контроля версий.","Запретите ручные изменения через консоль (click-ops); запускайте drift detection и исправляйте ресурсы, отклонившиеся от кода.","Управляйте различиями между средами (dev/stage/prod) переменными; стройте среды, близкие к продуктиву, из одного модуля.","Храните state-файл удаленно и с блокировкой (например, S3 + DynamoDB lock); не допускайте попадания секретов в state открытым текстом.","Проверяйте изменения через процесс plan/apply; ставьте этапы согласования перед продуктивом."],"level":"senior"},{"id":"automation-idempotency","title":"Автоматизация и идемпотентность","category":"Эксплуатация","summary":"Операционные задачи нужно превращать из вручную повторяемых команд в идемпотентную автоматизацию, которая дает тот же результат независимо от числа запусков.","points":["Пишите каждый скрипт идемпотентным, чтобы повторный запуск не завершался ошибкой и не портил состояние (например, CREATE IF NOT EXISTS вместо CREATE).","Привязывайте повторяющиеся задачи обслуживания (бэкап, очистка, статистика) к плановой автоматизации с журналированием; не оставляйте их на ручной запуск.","Встраивайте в скрипты явные проверки предусловий и безопасные выходы; повторный запуск незавершенной операции должен довести ее до конца с места остановки.","Запускайте автоматизацию из кода (Ansible/скрипты + CI); не зависьте от того, что кто-то набирает команды в терминале в продуктиве.","Обеспечьте поддержку dry-run для критичных операций; показывайте влияние до применения."],"level":"senior"},{"id":"observability","title":"Наблюдаемость (метрики/журналы/трассировки)","category":"Эксплуатация","summary":"Наблюдаемость объединяет метрики, журналы и распределенные трассировки, позволяя перейти от вопроса «что произошло» к вопросу «почему это произошло».","points":["Собирайте все три сигнала вместе: метрики (тренды), журналы (контекст), трассировки (путь запроса); ни один из них по отдельности не раскрывает первопричину.","Трассируйте запросы из конца в конец: сопоставляйте трассировки приложения с задержкой базы данных (с использованием стандартов вроде OpenTelemetry).","Пишите структурированные (JSON) журналы с correlation/trace id; журналы в свободном тексте затрудняют поиск.","Избегайте меток с высокой кардинальностью; держите под контролем взрывной рост кардинальности и стоимость.","Определите SLO и отслеживайте error budget; привязывайте наблюдаемость к бизнес-целям."],"level":"senior"},{"id":"alerting-strategy","title":"Стратегия оповещений (мало шума, по делу)","category":"Эксплуатация","summary":"Хорошая стратегия оповещений будит человека только при реальных условиях, требующих действий; шум вызывает усталость от оповещений и выгорание.","points":["Оповещайте о влиянии и пользовательском опыте (нарушения SLO), а не о симптомах; не вызывайте дежурного при каждом колебании метрики.","У каждого оповещения должны быть четкий владелец, условие и runbook; оповещение без определенного действия бесполезно.","Используйте гистерезис и длительность (for: 5m) в порогах; не давайте мгновенным всплескам вызывать оповещения.","Отделяйте вызов дежурного (paging) от уведомлений (тикет/email); вызывайте только при необходимости немедленного вмешательства.","Регулярно пересматривайте оповещения; исправляйте или удаляйте постоянно срабатывающие и игнорируемые.","Всегда покрывайте тихие катастрофы, такие как сбои бэкапов, заполненные диски и остановленная репликация."],"level":"senior"},{"id":"dr-testing","title":"Дисциплина учений DR/failover","category":"Бэкап и HA","summary":"План аварийного восстановления надежен только тогда, когда подтвержден регулярными реалистичными учениями; непротестированный план — лишь предположение.","points":["Планируйте учения по failover и восстановлению и проводите их периодически; бумажного плана, пересматриваемого раз в год, недостаточно.","Измеряйте фактические RTO и RPO на каждых учениях; сообщайте об отклонении от цели и улучшайте.","Репетируйте плановый failover в среде, близкой к продуктиву, с реальным трафиком; не ограничивайтесь запуском и остановкой standby.","Моделируйте сценарии вроде потери региона, отказа диска и повреждения данных на game days.","После каждых учений записывайте извлеченные уроки в runbook; превращайте шаги, которые выполняются по памяти, в автоматизацию."],"level":"expert"},{"id":"blue-green-deploy","title":"Blue-green / развертывание без простоя","category":"Эксплуатация","summary":"Развертывания blue-green и canary запускают новую версию рядом со старой и постепенно переключают трафик, минимизируя простой и риск.","points":["Поддерживайте две продуктивные среды (blue/green); разворачивайте новую версию в простаивающей, проверяйте ее, затем переключайте трафик за один шаг.","Если что-то пошло не так, немедленно переключите трафик обратно на старую среду; откат не должен требовать повторного развертывания.","При canary направляйте небольшой процент трафика на новую версию, следите за метриками и увеличивайте постепенно.","Отделяйте схему базы данных от развертывания: обе версии кода должны работать с одной и той же схемой (expand-contract).","Заранее определите проверки состояния и пороги автоматического отката; сократите время ожидания ручных решений."],"level":"senior"},{"id":"expand-contract","title":"Обратно совместимые изменения схемы (expand-contract)","category":"Проектирование","summary":"Шаблон expand-contract делит изменение схемы на добавление (expand), затем миграцию и, наконец, удаление старого (contract), обеспечивая переход без простоя.","points":["Сначала expand: добавьте новый столбец/таблицу обратно совместимым способом (nullable или со значением по умолчанию), не ломая старое.","Разверните приложение так, чтобы оно работало и со старой, и с новой схемой, используя dual-write или dual-read.","Переносите данные в новую структуру в фоне малыми пакетами и проверяйте их; не блокируйте одной гигантской миграцией.","Выполняйте contract только после перехода всех экземпляров на новую версию и завершения проверки: удалите старый столбец/код.","Выполняйте каждый шаг отдельным развертыванием; никогда не «добавляйте и удаляйте старое одновременно», иначе откат станет невозможным.","Добавляйте NOT NULL и другие ограничения в последнюю очередь, когда данные согласованы."],"level":"expert"},{"id":"secrets-management","title":"Управление секретами (пароли/ключи)","category":"Безопасность","summary":"Пароли баз данных, ключи API и строки подключения нельзя встраивать в код или конфигурацию; их следует хранить безопасно и ротировать с помощью централизованного менеджера секретов.","points":["Не встраивайте секреты в код, репозитории, env-файлы или образы; используйте хранилище секретов вроде Vault, AWS Secrets Manager или KMS.","Передавайте секреты приложениям во время выполнения; не оставляйте их на диске открытым текстом.","Регулярно ротируйте пароли и ключи; по возможности используйте короткоживущие автоматически генерируемые учетные данные.","Предоставляйте доступ по принципу наименьших привилегий и с журналом аудита; должно быть видно, кто, к какому секрету и когда обращался.","Сканируйте кодовую базу на утекшие секреты (git-secrets, trufflehog); немедленно отзывайте и ротируйте утекший секрет.","Делайте доступ людей временным и по согласованию (just-in-time), а не персональным и постоянным."],"level":"senior"},{"id":"cost-optimization","title":"Оптимизация затрат в облаке","category":"Эксплуатация","summary":"Затраты на облачные базы данных можно значительно сократить без ущерба для производительности за счет правильного выбора размера, многоуровневого хранения и очистки неиспользуемых ресурсов.","points":["Подбирайте размер экземпляров по фактическим метрикам использования; уменьшайте серверы с постоянно низкой загрузкой (right-sizing).","Используйте зарезервированную емкость/savings plans для предсказуемой базовой нагрузки; переменную нагрузку оставляйте on-demand.","Переносите холодные данные на более дешевый уровень хранения; отделяйте горячие данные от редко используемых.","Очищайте неиспользуемые ресурсы: осиротевшие snapshot, простаивающие реплики и заброшенные тестовые базы — это утечки затрат.","Назначайте затраты владельцам с помощью тегов и настройте бюджетные оповещения; узнавайте о сюрпризах сразу, а не в конце месяца.","Пересматривайте срок хранения бэкапов и число реплик относительно бизнес-потребностей; избыток — это и риск, и затраты."],"level":"senior"},{"id":"multi-tenancy","title":"Мультитенантное проектирование","category":"Проектирование","summary":"В мультитенантных системах модель изоляции (общая таблица, отдельная схема, отдельная база данных) — осознанный компромисс между безопасностью, стоимостью и масштабируемостью.","points":["Выберите модель изоляции: общая таблица (tenant_id), схема на тенанта или база данных на тенанта; у каждой свой баланс стоимости/изоляции.","В общей модели применяйте фильтр tenant_id в каждом запросе; гарантируйте отсутствие утечек на уровне движка с помощью row-level security (RLS).","Ставьте tenant_id первым в соответствующих индексах; предотвращайте межтенантные сканирования и горячие точки.","Применяйте лимиты ресурсов и квоты против эффекта noisy neighbor; один тенант не должен лишать ресурсов остальных.","С самого начала заложите путь (миграция тенанта) для переноса крупных или премиальных тенантов в отдельную схему/базу данных.","Продумайте бэкап, удаление и переносимость данных для каждого тенанта; вы должны уметь выполнять запросы на удаление по KVKK/GDPR."],"level":"expert"},{"id":"time-series-design","title":"Проектирование данных временных рядов","category":"Проектирование","summary":"Данные временных рядов особенны из-за большого объема записи и шаблонов запросов по времени; их следует проектировать с секционированием по дате, rollup и политикой хранения.","points":["Разделяйте данные RANGE-секционированием по метке времени; дешево очищайте старые данные через DROP секции.","Вместо накопления сырых данных как есть формируйте сводки с регулярным интервалом (rollup/continuous aggregate); пусть запросы быстро работают по сводкам.","Ставьте компоненты времени и тега (tag/источник) первыми в индексе; типичный шаблон запроса — «конкретный источник, конкретный диапазон».","Выбирайте правильный инструмент: при большом объеме движки временных рядов, такие как TimescaleDB и ClickHouse, могут быть гораздо эффективнее реляционных.","Определите политику хранения с самого начала; сырые данные храните недолго, агрегированные — долго.","Всегда храните время в UTC; обрабатывайте локальное время и переходы на летнее время на уровне запросов."],"level":"senior"},{"id":"event-sourcing","title":"Основы event sourcing / CQRS","category":"Проектирование","summary":"Event sourcing записывает не состояние, а события, которые его меняют; CQRS разделяет модели чтения и записи, чтобы каждая масштабировалась независимо.","points":["Вместо перезаписи текущего состояния накапливайте неизменяемый поток событий; получайте текущее состояние повторным проигрыванием событий.","С CQRS отделяйте модель записи (команды) от модели чтения (запросы); ускоряйте сторону чтения денормализованными проекциями.","Версионируйте схему событий и развивайте ее обратно совместимо; никогда не удаляйте старые события, преобразуйте их в новую версию через upcasting.","Примите, что проекции для чтения будут согласованы в конечном счете (eventually consistent), и учитывайте задержку в проекте.","Делайте snapshot, чтобы избежать затрат на повторное проигрывание длинного потока событий с самого начала каждый раз.","Используйте этот шаблон не везде, а в доменах, где нужны журнал аудита, сложные бизнес-правила и история во времени; не недооценивайте вносимую им сложность."],"level":"expert"},{"id":"data-retention","title":"Политика хранения и архивации данных","category":"Эксплуатация","summary":"Не все данные хранятся вечно; определенная политика хранения и архивации, основанная на юридических, бизнес- и стоимостных требованиях, обеспечивает и соответствие требованиям, и производительность.","points":["Определите срок хранения для каждого типа данных исходя из бизнес- и юридических (KVKK/GDPR) требований; расплывчатое «храним на всякий случай» — не политика.","Автоматически архивируйте или удаляйте данные с истекшим сроком; не оставляйте это ручной очистке, о которой забывают, пока таблицы раздуваются.","Храните горячие данные на быстром хранилище, а архивы — на дешевом/холодном; распределяйте по уровням по частоте доступа.","С самого начала заложите механизм обработки запросов на удаление/анонимизацию персональных данных (право на удаление).","Обеспечьте возможность извлечения и запроса архивных данных при необходимости; архивное не должно означать недоступное.","Фиксируйте удаления в журнале аудита; должно быть отслеживаемо, что, когда и почему было удалено."],"level":"senior"},{"id":"change-management","title":"Управление изменениями и безопасность продуктива","category":"Эксплуатация","summary":"Каждое изменение продуктивной базы данных должно быть проверенным, обратимым и отслеживаемым; неконтролируемое ручное вмешательство — самая частая причина простоев.","points":["Не выполняйте вручную ad-hoc DDL/DML в продуктиве; каждое изменение должно проходить через проверенную коллегами миграцию или скрипт.","Применяйте изменения в окно с низким трафиком при готовом плане отката; не трогайте продуктив без плана.","Сначала репетируйте рискованные команды в среде, близкой к продуктиву, с реальными объемами данных; измеряйте их длительность и влияние блокировок.","Ограничивайте и аудируйте доступ к продуктиву: кто, какую команду и когда выполнил, должно журналироваться (just-in-time access).","Перед удалением/обновлением проверьте условие WHERE и число затрагиваемых строк; выполняйте внутри транзакции и сначала подтвердите через SELECT.","Привязывайте изменения к календарю и коммуникации; не удивляйте другие команды необъявленными изменениями в продуктиве."],"level":"senior"}]},"guide/best-practices-3.json":{"topics":[{"id":"plan-reading-expert","title":"Как читать план выполнения","category":"Производительность","summary":"План выполнения показывает оценки оптимизатора и выбранные им физические операторы; узкое место скрывается в расхождении между оценочным и фактическим числом строк.","points":["Различайте методы доступа: Full/Table Scan — для широкого чтения больших таблиц, Index Seek/Range Scan — для селективных предикатов, Index Full Scan — для упорядоченного чтения; Rows Removed by Filter или разделение predicate/access показывают, где теряется селективность.","Сопоставляйте метод соединения с нагрузкой: Nested Loop идеален для малого внешнего набора + индексированного внутреннего, Hash Join — для больших equi-join, Merge Join — для двух заранее отсортированных наборов; неверный выбор обычно следствие плохой оценки кардинальности.","Ищите ошибки оценки кардинальности: сравнивайте оценочные и фактические строки через EXPLAIN ANALYZE (PG), DBMS_XPLAN ALLSTATS LAST (Oracle) или SET STATISTICS PROFILE (MSSQL); отклонение более чем в 10x говорит о том, что выбор плана может быть в корне неверным.","Читайте план по реальной стоимости: найдите оператор, потребляющий больше всего времени/логических чтений (logical reads, buffers); единица «cost» оптимизатора — оценка, а не фактическое узкое место.","Отлавливайте предупреждения о сбросе в temp: оператор Hash/Sort, не помещающийся в память и сбрасывающийся на диск (PG 'Sort Method: external merge', MSSQL Sort/Hash Warning с SpillToTempDb в плане), указывает на недостаток work_mem/области сортировки.","Читайте дерево операторов изнутри наружу и снизу вверх: внутренние узлы выполняются первыми, строки передаются вверх; вычисляйте общую стоимость оператора, умножая loops на actual time."],"level":"expert"},{"id":"parameter-sniffing-deep","title":"Parameter sniffing и bind peeking","category":"Производительность","summary":"Оптимизатор строит и кэширует план на основе значения параметра первого вызова; когда последующие значения с другим распределением наследуют этот план, производительность может резко измениться.","points":["Распознайте симптом: один и тот же запрос быстр с одними параметрами и очень медленен с другими; это показывает, что кэшированный план скомпилирован для нетипичного «первого» значения.","Риск высок на столбцах с перекошенным распределением; гистограммы делают оптимизатор чувствительным к значениям, а единственный кэшированный план — нечувствительным, и это противоречие — источник проблемы.","В MSSQL OPTION (RECOMPILE) строит план при каждом выполнении, OPTIMIZE FOR UNKNOWN выбирает сбалансированный план по средней плотности, а OPTIMIZE FOR (@p = value) закрепляет типичное значение.","В Oracle adaptive cursor sharing и bind-aware курсоры создают разные планы для перекошенных значений; в PG выбор между custom и generic планом управляется через plan_cache_mode.","Для постоянного решения разделяйте планы по значениям: проектируйте разделение операторов, фильтрованные/частичные индексы или отдельный путь доступа для сильно нетипичных значений.","В OLTP избыточный RECOMPILE добавляет затраты CPU и компиляции (hard parse); измеряйте и соблюдайте баланс на часто выполняемых запросах."],"level":"expert"},{"id":"plan-stability","title":"Стабильность планов: baselines, hints и plan guides","category":"Производительность","summary":"Специфичные для движка механизмы закрепления используются, чтобы зафиксировать проверенные планы критичных запросов и предотвратить неожиданные регрессии планов при изменениях статистики/версии.","points":["В Oracle закрепляйте одобренные планы через SQL Plan Baselines (SPM); новые планы лишь собираются как кандидаты, пока не будут «приняты» (accepted), и контролируемо проверяются через evolve.","В MSSQL выявляйте регрессию планов через Query Store и принудительно применяйте заведомо хороший план через 'Force Plan'; Automatic Plan Correction может это автоматизировать.","В PostgreSQL нет встроенной фиксации планов; стабильность достигается расширением pg_hint_plan либо переписыванием запросов, расширенной статистикой и проектированием индексов.","Используйте hints как крайнюю меру, с обоснованием и комментариями; hint предполагает определенную физическую схему и распределение данных и становится вредным при их изменении.","Периодически проверяйте перечень baselines/принудительных планов; закрепленные планы со временем, по мере роста данных, могут терять оптимальность, поэтому не попадайте в ловушку «стабильно, но медленно».","Сохраняйте планы критичных запросов перед обновлением версии; готовьте baselines как страховку от изменений поведения оптимизатора."],"level":"expert"},{"id":"latch-mutex-contention","title":"Конкуренция latch, mutex и spinlock","category":"Производительность","summary":"Latch/мьютексы — легковесные блокировки, защищающие структуры в памяти; при высокой конкурентности конкуренция за них сжигает CPU в циклах spinlock и останавливает масштабирование.","points":["Различайте блокировки и latch: блокировки защищают логические данные (строку/таблицу) на время транзакции, latch защищают физические структуры памяти (буферы, хеш-цепочки) на микросекунды; методы их диагностики различаются.","В Oracle конкуренция за latch 'cache buffers chains' обычно указывает на горячий блок; частая причина — маленькая часто читаемая таблица или передний край монотонно возрастающего индекса (right-hand index).","Вставки с последовательно возрастающими ключами (identity/sequence) вызывают конкуренцию за последнюю страницу/блок (hot page, PAGELATCH); распределяйте их hash-секционированием, reverse-key индексом или рандомизированным ключом.","В MSSQL отслеживайте статистику spinlock и ожидания PAGELATCH_*; при конкуренции за выделение в tempdb используйте несколько файлов данных и правильную конфигурацию tempdb.","Конкуренция spinlock проявляется как высокий CPU при низкой пропускной способности; решение — снизить конкуренцию на уровне кода/архитектуры, а добавление оборудования часто ухудшает ситуацию.","Учет NUMA и ограничение числа подключений (пулинг) снижают конкуренцию latch; избыточный параллелизм может увеличить давление на latch."],"level":"expert"},{"id":"wait-event-methodology","title":"Методология анализа событий ожидания","category":"Производительность","summary":"Управляйте производительностью, измеряя, где система тратит время на ожидание, а не угадывая; нисходящий анализ ожиданий — научный метод, систематически сужающий поиск узкого места.","points":["Разложите DB time на составляющие: отделите время на CPU от времени ожиданий; начинайте с самой большой составляющей и не уходите в микрооптимизацию.","Найдите главное событие ожидания через ASH/AWR и 'Top Wait Events' в Oracle, pg_stat_activity + pg_wait_sampling в PG и sys.dm_os_wait_stats и Query Store в MSSQL.","Интерпретируйте класс ожидания: ожидания I/O указывают на хранилище/планы, ожидания блокировок — на проектирование конкурентности, ожидания latch — на горячие точки, а ожидания сети/commit — на проблемы конфигурации.","Спускайтесь до уровня сессии/запроса: начните с общих ожиданий, перейдите к SQL с наибольшим вкладом, а затем к плану этого SQL, чтобы изолировать причину (drill-down).","Привязывайте измерение к окну нагрузки: берите разницу (delta) накопительных счетчиков между двумя точками; итоговые значения вводят в заблуждение, важны скорости и интервалы времени.","Отфильтруйте idle/безвредные ожидания как шум; не каждое ожидание — проблема, важны foreground-ожидания, влияющие на время отклика пользователю."],"level":"expert"},{"id":"plan-regression-detection","title":"Выявление регрессий планов выполнения","category":"Производительность","summary":"Внезапно замедлившийся запрос обычно вызван изменением плана, а не кода; проактивное выявление регрессий дешевле реактивного тушения пожаров.","points":["Храните и сравнивайте историю планов: отслеживайте изменения планов и тренды длительности/ресурсов для одного sql_id/query_hash через MSSQL Query Store и Oracle AWR/SQL Monitor.","Определите триггер регрессии: обновление статистики, рост объема данных, обновление версии, изменение параметров или удаленные индексы — самые частые причины.","Настройте автоматические оповещения: поднимайте тревогу, когда меняется plan hash и средняя длительность/логические чтения превышают порог; отслеживайте метрики на одно выполнение, а не накопительные.","Подготовьте быстрый путь отката: временно закрепите заведомо хороший план через Force Plan (MSSQL) или SQL Plan Baseline (Oracle) и исправьте первопричину позже.","При регрессиях, вызванных статистикой, изучите заблокированную/текущую статистику и актуальность гистограмм; автоматический сбор статистики может быть запланирован на критичные часы.","Проводите регрессионные тесты перед обновлением: воспроизведите репрезентативную нагрузку на старом и новом оптимизаторе и сообщите о различиях планов (подход, подобный Oracle SPA)."],"level":"expert"},{"id":"mvcc-vacuum-internals","title":"Внутреннее устройство MVCC и vacuum/undo","category":"Внутреннее устройство","summary":"MVCC предотвращает блокировки между читателями и писателями, сохраняя старые версии строк; как и где хранятся эти версии (undo или внутри таблицы), определяет стратегию обслуживания.","points":["В PostgreSQL старые версии остаются в самой таблице как мертвые кортежи; VACUUM очищает их, иначе возникают bloat и рост индексов.","Oracle и MySQL/InnoDB хранят старые версии в сегментах undo/rollback; если долгое чтение исчерпывает undo, появляются ошибки вроде 'ORA-01555 snapshot too old'.","Настройте autovacuum под нагрузку: снизьте scale factor и уменьшите cost delay на таблицах с интенсивными update/delete; задайте агрессивным таблицам собственные настройки.","Поддерживайте visibility map в исправном состоянии; от нее зависят index-only scan и отслеживание замороженных кортежей, и при пренебрежении производительность ухудшается.","Отслеживайте риск переполнения ID транзакций (wraparound): следите за метрикой datfrozenxid/age в PG и не допускайте запуска агрессивного/anti-wraparound vacuum в рабочие часы.","Поощряйте HOT-обновления (Heap-Only Tuple): обновления, не меняющие индексированный столбец, выполняются на месте без роста индекса; подходящий fillfactor этому способствует."],"level":"expert"},{"id":"wal-redo-log-mechanics","title":"Механика WAL/redo/журнала транзакций","category":"Внутреннее устройство","summary":"Надежность достигается записью изменения в журнал до записи страниц данных на диск; скорость и объем потока журнала определяют и надежность, и производительность записи.","points":["Поймите принцип write-ahead: чтобы commit вернул управление, достаточно, чтобы соответствующие записи журнала были на надежном носителе; грязные страницы данных записываются позже, отложенно.","Настройте group commit и поведение сброса журнала: балансируйте частоту fsync с требованиями надежности нагрузки через commit_delay/synchronous_commit в PG и поведение log writer в Oracle.","Отслеживайте скорость генерации журнала (WAL/redo generation); резкий рост указывает на массовые обновления, полные образы страниц после контрольной точки (full_page_writes/FPI) или обслуживание индексов и увеличивает отставание DR.","Журнал — источник не только для восстановления, но и для репликации и CDC; логическая репликация и WAL shipping питаются этим потоком, поэтому проектируйте политику архивации/очистки соответственно.","Отделите диск журнала от диска данных и разместите его на хранилище с низкой задержкой; запись журнала последовательна и чувствительна к задержке, и пропускная способность commit напрямую от нее зависит.","Защищайтесь от неконтролируемого роста журнала: в MSSQL используйте правильную модель восстановления и регулярные бэкапы журнала; в PG следите, чтобы архивация/слоты репликации не отставали."],"level":"expert"},{"id":"buffer-pool-management","title":"Управление buffer/shared pool и вытеснение","category":"Внутреннее устройство","summary":"Буферный пул сокращает дисковый I/O, удерживая горячие данные в памяти; алгоритм, решающий, какая страница и когда вытесняется, определяет коэффициент попаданий и стабильность.","points":["Не ориентируйтесь только на коэффициент попаданий в буферный кэш; даже высокое значение может скрывать задержку физических чтений, поэтому смотрите также на абсолютный I/O и время ожиданий.","Поймите алгоритм вытеснения: против загрязнения чистого LRU последовательными сканированиями PG использует clock-sweep, Oracle — вариант touch-count/LRU, а InnoDB — LRU со вставкой в середину (midpoint insertion).","Не давайте большим разовым сканированиям вымывать горячие данные из пула (cache pollution); защищайте их отдельными буферными пулами или стратегиями доступа, дружественными к сканированиям.","Управляйте долей грязных страниц и давлением записи: слишком много грязных страниц вызывает всплески I/O при контрольной точке, слишком агрессивная запись создает лишний I/O; измеряйте баланс.","Подбирайте размер памяти под рабочий набор; цель — покрыть активно используемый набор, а не все данные, и избыточный размер дает убывающую отдачу.","В shared pool Oracle отслеживайте конкуренцию library/dictionary cache и частые hard parse; использование bind-переменных вместо SQL с литералами снижает давление на shared pool."],"level":"expert"},{"id":"checkpoint-tuning","title":"Настройка контрольных точек","category":"Внутреннее устройство","summary":"Контрольная точка сокращает время восстановления, записывая грязные страницы на диск; слишком частые контрольные точки создают пики I/O, слишком редкие — долгое восстановление, и этот компромисс нужно настраивать.","points":["Балансируйте время восстановления и I/O во время работы: редкие контрольные точки означают меньшую нагрузку записи, но более долгое восстановление после сбоя, частые — наоборот.","В PG распределяйте контрольные точки во времени через checkpoint_timeout и max_wal_size; сглаживайте пик I/O, распределяя запись по интервалу через checkpoint_completion_target.","Indirect checkpoint в MSSQL (target recovery time) обеспечивает непрерывную фоновую запись; делает задержку предсказуемой, предотвращая внезапные большие волны контрольных точек.","Сопоставляйте частоту контрольных точек со скоростью генерации журнала/WAL; в периоды интенсивной записи отслеживайте, срабатывает ли триггер по размеру, а не по времени.","full_page_writes (PG) записывает в журнал полную страницу при первом изменении после контрольной точки; при слишком частых контрольных точках объем WAL резко растет, увеличивая затраты на DR и диск.","Отлавливайте симптомы «провала на контрольной точке»: рост задержки записи и ожиданий fsync во время контрольных точек; пересмотрите очередь хранилища и распределение."],"level":"expert"},{"id":"statistics-strategy","title":"Стратегия сбора статистики","category":"Внутреннее устройство","summary":"Качество решений оптимизатора зависит от актуальности и детальности статистики; частота выборки, гистограммы и расширенная статистика определяют оценки кардинальности.","points":["Выбирайте частоту выборки по размеру данных и перекосу: слишком малая выборка дает неверные оценки на перекошенных распределениях, слишком большая создает лишние затраты на сбор.","Моделируйте перекос гистограммами: PG default_statistics_target, гистограммы Oracle FREQUENCY/HYBRID и гистограммы статистики MSSQL показывают оптимизатору частые и несбалансированные значения.","Учитывайте корреляцию между столбцами с помощью расширенной статистики: PG CREATE STATISTICS (ndistinct/dependencies) и extended stats в Oracle исправляют ошибки оценки на взаимозависимых столбцах.","Отвяжите расписание сбора от нагрузки: не допускайте, чтобы автоматическое задание статистики/gather запускалось в критичные часы и вызывало I/O и нестабильность планов.","Управляйте риском устаревшей статистики на быстро растущих таблицах; автоматические обновления по порогу могут запаздывать, поэтому добавьте ручной/плановый сбор для критичных таблиц.","Помните, что изменения статистики могут вызывать регрессии планов; в критичных средах блокируйте статистику (locked stats) или публикуйте ее контролируемо через pending publish."],"level":"expert"},{"id":"partition-pruning-join","title":"Partition pruning и partition-wise join","category":"Масштаб/Архитектура","summary":"Секционирование не просто делит таблицу; настоящий выигрыш в том, что запросы никогда не читают нерелевантные секции (pruning), а соединения выполняются параллельно по секциям.","points":["Выбирайте ключ секционирования по самому частому и селективному фильтру; pruning работает, только когда предикат запроса включает ключ секционирования, иначе сканируются все секции.","Различайте статический и динамический pruning: константные предикаты исключают секции при компиляции, значения bind/join — во время выполнения (runtime pruning); проверяйте план соответственно.","Для partition-wise join секционируйте обе таблицы по одному ключу совместимой схемой; оптимизатор сводит большое соединение к малым посекционным соединениям и распараллеливает их.","Балансируйте число и гранулярность секций: слишком много секций — затраты на метаданные и планирование, слишком мало — слабый pruning; подбирайте их под шаблон доступа.","Совмещайте управление жизненным циклом с секционированием: выполняйте DETACH/DROP или архивацию старых секций; это дает мгновенную очистку с малым журналированием вместо DELETE.","Помните, что функции/преобразования типов или выражения, скрывающие ключ, ломают pruning; проверяйте число сканируемых секций через EXPLAIN."],"level":"expert"},{"id":"shard-key-selection","title":"Выбор ключа шардинга и горячие точки","category":"Масштаб/Архитектура","summary":"Ключ шардинга определяет судьбу распределенной системы; неудачный выбор сваливает нагрузку на один узел (hot shard), останавливает масштабирование и делает межшардовые запросы дорогими.","points":["Выбирайте ключ с высокой кардинальностью, равномерно распределяющий нагрузку; ключ с низкой кардинальностью или временной/монотонно возрастающий сваливает нагрузку на один шард и создает горячую точку.","Проектируйте ключ так, чтобы самые частые запросы выполнялись на одном шарде; межшардовые (scatter-gather) запросы многократно увеличивают задержку и затраты на координацию.","Hash-шардинг выравнивает нагрузку, но рассеивает диапазонные запросы; range-шардинг хорош для диапазонных запросов, но повышает риск горячих точек; выбирайте по шаблону доступа.","Минимизируйте перераспределение при добавлении/удалении узлов с помощью consistent hashing; фиксированный хеш по модулю перемещает почти все данные при изменении числа узлов.","Проектируйте план resharding с самого начала; менять ключ позже дорого и рискованно, поэтому заранее моделируйте сценарий роста.","При разделении по тенантам/клиентам подготовьте выделенный шард или стратегию подсекционирования (split), чтобы гигантский тенант (whale) не заполнил один шард."],"level":"expert"},{"id":"read-write-splitting","title":"Разделение чтения и записи и маршрутизация на реплики","category":"Масштаб/Архитектура","summary":"Направление чтений на реплики разгружает пишущий primary; но поскольку отставание репликации может нарушить гарантии согласованности, маршрутизацию нужно выполнять осознанно.","points":["Распределяя нагрузку чтения по репликам, постоянно отслеживайте отставание репликации; устаревшая реплика может показывать пользователям старые данные.","Направляйте потоки, требующие read-your-writes (пишущий пользователь сразу читает), на primary или гарантируйте свежую реплику через causal reads/токены.","Выполняйте маршрутизацию на уровне инфраструктуры, а не приложения: распределяйте прозрачно через proxy/router (ProxySQL для MySQL; для PG — Pgpool-II или HAProxy с отдельными endpoint для чтения/записи, так как PgBouncer не разделяет чтение и запись; в MSSQL — read-only routing через application intent).","Исключайте реплику из пула при превышении порога отставания; отправка чтений на сильно отстающую реплику порождает несогласованность данных под иллюзией «масштабирования».","Изолируйте тяжелые, терпимые к отставанию чтения, такие как аналитика/отчетность, на выделенной реплике; защищайте OLTP-реплики от этой нагрузки.","Проверяйте, что маршрутизация чтений автоматически обновляется в сценарии failover; маршрутизатор, не знающий о новом primary, вызывает split-brain или простой."],"level":"expert"},{"id":"connection-pool-sizing","title":"Математика размера пула подключений","category":"Масштаб/Архитектура","summary":"Размер пула определяется расчетом на основе закона Литтла и числа ядер, а не интуицией; слишком много подключений снижает пропускную способность из-за конкуренции и переключения контекста.","points":["Применяйте закон Литтла: требуемая конкурентность ≈ частота запросов (пропускная способность) × среднее время обработки; выводите целевую конкурентность из измеренных значений, а не из догадок.","Небольшой пул часто быстрее: когда активных подключений больше, чем физический параллелизм ядер/дисков, переключение контекста CPU и конкуренция latch снижают пропускную способность.","В качестве грубой отправной точки используйте основной пул с числом подключений, близким к числу ядер (например, небольшое число реальных подключений за PgBouncer), и настраивайте, измеряя под нагрузкой.","Планируйте пул на стороне приложения вместе с max_connections базы данных; N экземпляров приложения × размер пула не должны превышать лимит базы данных.","На движках с высокой стоимостью процесса на подключение, таких как PG, используйте PgBouncer/transaction pooling; каждое подключение стоит памяти и планирования.","Отслеживайте насыщение пула, время ожидания в очереди и тайм-ауты; постоянно растущая очередь сигнализирует либо об узком месте, либо о недостаточном размере, поэтому не увеличивайте пул вслепую."],"level":"expert"},{"id":"lock-escalation-deadlock","title":"Эскалация блокировок и шаблоны предотвращения взаимоблокировок","category":"Надежность","summary":"Множество блокировок строк может эскалироваться до блокировки таблицы и убить конкурентность; взаимоблокировка — устранимое следствие несогласованного порядка блокировок.","points":["Разбивайте большие массовые операции, вызывающие эскалацию блокировок (batch/chunk); блокировка миллионов строк в одной транзакции вызывает эскалацию и блокировки.","Базовый шаблон предотвращения взаимоблокировок — согласованный порядок блокировок: все пути кода должны блокировать ресурсы в одном и том же порядке (например, по порядку первичного ключа).","Делайте транзакции короткими и выполняйте одну атомарную единицу работы; транзакции с взаимодействием с пользователем, удаленными вызовами или долгими вычислениями держат блокировки неоправданно долго.","Выбирайте уровень изоляции осознанно: излишне высокая изоляция вызывает лишние блокировки, излишне низкая — аномалии; в движках с MVCC чтения и так избегают большинства блокировок.","Собирайте графы/трассировки взаимоблокировок (MSSQL deadlock graph, PG log_lock_waits, трассировка Oracle); журналируйте жертву и прослеживайте повторяющиеся шаблоны до первопричины.","Реализуйте логику повтора: повторяйте транзакцию-жертву взаимоблокировки идемпотентно; это страховка от конкуренции, неизбежной по проекту."],"level":"expert"},{"id":"long-transaction-bloat","title":"Долгие транзакции и управление bloat/undo","category":"Надежность","summary":"Оставленная открытой долгая транзакция блокирует очистку старых версий в движках с MVCC, запуская цепочку bloat, давления на undo и обвала производительности.","points":["Отслеживайте долгие/открытые транзакции и настройте оповещения: idle in transaction в PG и долгие запросы, удерживающие активный undo в Oracle, останавливают очистку (vacuum/undo reclamation).","В PG одна долгая транзакция не дает VACUUM удалять мертвые кортежи; таблицы и индексы раздуваются, и одна «зомби»-сессия может замедлить всю базу данных.","Автоматически завершайте зависшие транзакции через idle_in_transaction_session_timeout и statement_timeout; выстройте линию обороны от утечек в приложении.","В Oracle/InnoDB долгое чтение исчерпывает undo и приводит к 'snapshot too old'; подбирайте объем/retention undo под самый долгий запрос нагрузки.","Разбивайте пакетные задания короткими интервалами commit; одна гигантская транзакция накапливает undo/WAL и опасно удлиняет время отката.","Измеряйте и устраняйте существующий bloat: измеряйте bloat через pgstattuple и возвращайте место и эффективность индексов через VACUUM FULL/pg_repack или онлайн-реорганизацию."],"level":"expert"},{"id":"dr-game-day","title":"Учения DR (game day) и проверка RPO/RTO","category":"Надежность","summary":"Непротестированный план бэкапа и failover — это предположение; способность к DR доказывается только измерением RPO/RTO на реалистичных учениях.","points":["Выводите RPO и RTO из бизнес-требований и измеряйте на практике: отставание бэкапа/репликации определяет RPO, время восстановления + проверки определяет RTO; сравнивайте цель с реальностью.","Регулярно и автоматически восстанавливайте бэкапы: бэкап, который ни разу не восстанавливали, — бэкап Шредингера; без теста восстановления утверждение о наличии бэкапа не проверено.","Планируйте учения game day в среде, близкой к продуктиву, с реалистичными сценариями сбоев (потеря primary, отказ региона, повреждение данных).","Выполняйте процедуры failover и failback от начала до конца; включайте маршрутизацию DNS/подключений, переподключение приложения и повышение реплики.","Подтвердите возможность восстановления на момент времени (PITR): проверьте, что можете вернуться к определенному моменту с архивами журнала + полными бэкапами в сценарии логического повреждения.","По итогам каждых учений фиксируйте измеренные RTO/RPO, неожиданности и пункты действий; обновляйте runbook и проверяйте улучшение на следующих учениях."],"level":"expert"},{"id":"observability-slo","title":"Наблюдаемость и SLO/golden signals","category":"Надежность","summary":"Чтобы управлять базой данных по влиянию на бизнес, привязывайте golden signals (задержка, трафик, ошибки, насыщение) к SLO и расставляйте приоритеты с помощью error budget.","points":["Отслеживайте четыре golden signals: задержку, трафик (пропускная способность), частоту ошибок (errors) и насыщение (saturation); они обобщают состояние без погружения в первопричину.","Измеряйте задержку перцентилями (p95/p99), а не средними; среднее скрывает хвостовую задержку и худший пользовательский опыт.","Определите SLO и error budget; оповещайте о симптомах (нарушения SLO), а не о каждом колебании низкоуровневой метрики, чтобы избежать усталости от оповещений.","Применяйте к ресурсам метод USE (Utilization, Saturation, Errors): наблюдайте насыщение CPU, памяти, дискового I/O и пула подключений по отдельности.","Сопоставляйте мониторинг между уровнями: видьте цепочку запрос -> событие ожидания -> насыщение ресурса -> бизнес-метрика на одной оси времени.","Настраивайте метрики + журналы + трассировки вместе; метрики сами по себе отвечают на вопрос «что», журналы/трассировки — на вопрос «почему», и без них MTTR растет."],"level":"expert"},{"id":"least-privilege-roles","title":"Наименьшие привилегии и проектирование ролей","category":"Безопасность","summary":"Когда у каждой учетной записи есть только минимальные привилегии, нужные для ее работы, радиус поражения при взломе сужается; роли дают управляемую абстракцию вместо прямой выдачи привилегий.","points":["Выдавайте привилегии ролям, а не пользователям; проектируйте роли по функциям (чтение, запись, DDL, эксплуатация) и назначайте пользователей ролям, избегая прямых grant.","Давайте учетным записям приложений только необходимые привилегии на объекты и операции; DDL, SUPERUSER/DBA и широкие привилегии на схему не должны быть у продуктивной учетной записи приложения.","Персонализируйте и аудируйте доступ администраторов-людей: используйте индивидуальные учетные записи + повышение прав по запросу (JIT/break-glass) вместо общей учетной записи администратора.","Отзывайте PUBLIC/широкие привилегии по умолчанию; настройте default privileges и контроль на уровне схемы, чтобы права по умолчанию не распространялись на новые объекты.","Регулярно пересматривайте привилегированные учетные записи (access recertification); периодически очищайте неиспользуемые учетные записи, записи ушедших сотрудников и записи с избыточными привилегиями.","Обеспечивайте разделение обязанностей; один и тот же человек не должен иметь возможность менять продуктив и стирать журнал аудита, поэтому защищайте запись аудита отдельной привилегией."],"level":"expert"},{"id":"encryption-decision","title":"Решения о шифровании: at-rest, in-transit, TDE","category":"Безопасность","summary":"Шифрование выбирается исходя из модели угроз; каждый уровень защищает от другой атаки, и управление ключами часто важнее алгоритма шифрования.","points":["Уточните угрозу: at-rest (украденные диски/бэкапы), in-transit (прослушивание сети) и уровень приложения (не раскрывается никому, включая DBA) — разные виды защиты; решите, какие вам нужны.","Для in-transit требуйте TLS на подключениях клиент-сервер; не отключайте проверку сертификатов, иначе вы уязвимы для атак man-in-the-middle (MITM).","Для at-rest сравните TDE с вариантами шифрования файловой системы/диска; TDE защищает файлы данных и бэкапы, но не данные в памяти и не авторизованный доступ DBA.","Для самых чувствительных полей используйте шифрование на уровне приложения/столбца или клиентский подход вроде Always Encrypted, чтобы даже DBA не видел открытый текст.","Серьезно относитесь к управлению ключами: храните ключи в KMS/HSM отдельно от данных и планируйте ротацию и контроль доступа; если ключ утек, шифрование бесполезно.","Измеряйте влияние на производительность и функциональность: индексирование/поиск ограничены на зашифрованных столбцах, а TDE добавляет затраты CPU; принимайте решение как измеренный компромисс."],"level":"expert"},{"id":"audit-strategy","title":"Стратегия аудита","category":"Безопасность","summary":"Журнал аудита дает доказательства событий безопасности и соответствия требованиям; что журналируется, куда направляется журнал и как долго хранится, определяет баланс между шумом и доказательствами.","points":["Определяйте, что аудировать, исходя из угроз и требований соответствия: в приоритете привилегированные операции, входы/неудачные входы, изменения схемы (DDL) и доступ к чувствительным данным.","Не попадайте в ловушку «журналировать все»; избыточный аудит ухудшает производительность и топит сигнал в шуме, поэтому выборочно и целенаправленно ориентируйтесь на критичные события.","Пишите журнал аудита в отдельное защищенное место, которое продуктивный DBA не может изменить (внешний сборщик журналов/SIEM); предотвращайте подделку журнала разделением обязанностей.","Используйте возможности движка: настраивайте уровень детализации под свои нужды через Oracle Unified/Fine-Grained Audit, MSSQL SQL Audit и PG pgAudit.","Задавайте срок хранения согласно требованиям соответствия (KVKK/GDPR/PCI-DSS и т.д.) и автоматизируйте политику архивации/очистки; сохраняйте целостность журнала.","Не просто собирайте журнал; регулярно просматривайте его и привязывайте к оповещениям: аудит приносит пользу, только когда выявляет аномалии и превращает их в действия, а не как мертвый архив."],"level":"expert"}]},"guide/best-practices-4.json":{"topics":[{"id":"oracle-rac-cache-fusion","title":"Oracle RAC: Cache Fusion и interconnect","category":"Oracle","summary":"В RAC несколько экземпляров используют одну базу данных; Cache Fusion поддерживает согласованность, передавая блоки между узлами через interconnect, а не через диск.","points":["Когда экземпляру нужен блок, находящийся в буферном кэше другого экземпляра, Cache Fusion передает его из RAM в RAM через частный interconnect; он не записывает блок на диск и не читает его обратно (disk ping).","Global Cache Service (процессы GCS/LMS) управляет владением блоками и режимом (shared/exclusive), а Global Enqueue Service (GES) управляет блокировками и enqueue.","У каждого блока есть master-узел; расположение блоков отслеживается в Global Resource Directory и может переназначаться через DRM (Dynamic Resource Mastering) в зависимости от интенсивности доступа.","Для interconnect нужна выделенная сеть с низкой задержкой; используйте Jumbo Frames (MTU 9000) и при необходимости RDMA/RoCE или InfiniBand и никогда не делите ее с публичной сетью.","События ожидания 'gc buffer busy' и 'gc cr/current block busy' указывают на конкуренцию за горячие блоки; могут помочь увеличение CACHE последовательностей, reverse key индексы или распределение нагрузки секционированием.","Если статистика gc blocks lost продолжает расти, на interconnect есть потеря пакетов; проверьте NIC, буфер коммутатора и размеры буферов сокетов (rmem/wmem)."],"level":"expert"},{"id":"oracle-rac-node-eviction","title":"Oracle RAC: исключение узла (node eviction) и split-brain","category":"Oracle","summary":"Clusterware исключает неотвечающий узел из кластера, чтобы предотвратить split-brain; решение принимается на основе сетевого heartbeat, дискового heartbeat и большинства voting disk.","points":["CSSD отслеживает два отдельных heartbeat: сетевой heartbeat через interconnect (misscount, по умолчанию 30 s) и дисковый heartbeat через voting disks (disktimeout, по умолчанию 200 s).","При split-brain выживающий подкластер (cohort) определяется большинством voting disk; поэтому число voting disk должно быть нечетным (3 или 5), и они должны находиться в разных failure group.","Исключение обычно выполняется перезагрузкой узла или fencing без перезагрузки (перезапуск только стека); цель — не дать сбойному узлу писать в общее хранилище.","Частые причины исключения: потеря interconnect, нехватка CPU для CSSD при высокой нагрузке, задержка I/O voting disk и проблемы доступа к OCR/voting disk.","Для анализа первопричины просматривайте ocssd.log, трассировки CSSD, alert log и сообщения ОС вместе с данными CHM/oclumon (Cluster Health Monitor)."],"level":"expert"},{"id":"oracle-asm-redundancy-rebalance","title":"Oracle ASM: избыточность дисковых групп и rebalance","category":"Oracle","summary":"ASM защищает данные, распределяя их (striping) по дискам дисковой группы и зеркалируя по failure group; при добавлении или удалении дисков автоматически перебалансирует экстенты.","points":["Уровни избыточности: EXTERNAL (без зеркалирования, защита возложена на массив хранения), NORMAL (2 копии, выдерживает потерю 1 диска), HIGH (3 копии, выдерживает 2 одновременные потери).","ASM зеркалирует failure group, а не диски; основной экстент находится в одной failure group, а его зеркальные копии — в других, поэтому данные не теряются даже при отказе целой failure group.","Добавление или удаление диска запускает rebalance, перераспределяющий экстенты; ASM_POWER_LIMIT (0-1024) задает баланс между скоростью и потреблением ресурсов.","Размер AU (Allocation Unit) задается при создании дисковой группы; для больших баз данных AU 4 MB повышает эффективность I/O.","Voting disks и OCR можно хранить в дисковой группе с избыточностью normal/high; quorum failure group используется только для voting disks и не содержит экстентов данных."],"level":"expert"},{"id":"oracle-data-guard-modes","title":"Oracle Data Guard: типы standby и режимы защиты","category":"Oracle","summary":"Data Guard обеспечивает аварийное восстановление, передавая redo на standby; выбор physical/logical standby и трех режимов защиты задает баланс между потерей данных и доступностью.","points":["Physical standby — поблочно идентичная копия primary (Redo Apply/MRP); с Active Data Guard ее можно запрашивать в режиме только для чтения, пока она открыта. Logical standby преобразует redo в SQL и применяет его (SQL Apply) и может содержать другие структуры/индексы.","Maximum Performance (по умолчанию): redo передаётся асинхронно (ASYNC), и primary не ждёт подтверждения; нулевое влияние на производительность, но небольшой риск потери данных.","Maximum Availability: redo передаётся синхронно (SYNC/AFFIRM); если standby недоступен, primary не останавливается, а переходит к поведению MAX PERFORMANCE и ресинхронизируется после возвращения standby.","Maximum Protection: primary не выполняет commit, пока хотя бы один standby не подтвердит получение; гарантирует нулевую потерю данных, но primary останавливается при потере всех standby.","Fast-Start Failover (FSFO) использует процесс observer для автоматической отработки отказа при сбое primary; RPO=0 достигается с помощью FastStartFailoverThreshold и синхронного транспорта.","Отслеживайте отставание применения на standby через v$dataguard_stats и метрики 'apply lag'/'transport lag'; real-time apply применяет redo до его архивирования."],"level":"expert"},{"id":"oracle-exadata-offload","title":"Oracle Exadata: Smart Scan и storage indexes","category":"Oracle","summary":"В Exadata ячейки хранения берут на себя часть обработки запроса (offload) и возвращают узлу БД только релевантные данные; Smart Scan, storage indexes и HCC резко снижают I/O.","points":["Для запросов с полным сканированием Smart Scan выполняет фильтрацию по предикатам и проекцию столбцов в ячейке хранения; узлу БД возвращаются только нужные строки/столбцы, а не блоки, что снижает трафик по интерконнекту.","Smart Scan работает только при direct path reads; он не срабатывает для блоков, прочитанных из buffer cache, и для некоторых операций; оценивайте выигрыш по статистикам 'cell physical IO bytes eligible for predicate offload' и 'cell physical IO interconnect bytes returned by smart scan' ('cell physical IO bytes saved by storage index' показывает только экономию за счёт storage index).","Storage index хранит в памяти минимальные/максимальные значения столбцов для каждой области хранения размером 1 MB; если предикат запроса выходит за этот диапазон, область пропускается без чтения (работает как негативный индекс).","Storage indexes не сохраняются на диске; они живут в памяти и перестраиваются при перезапуске ячейки. Физически упорядоченные данные (кластеризация) повышают долю пропуска.","HCC (Hybrid Columnar Compression) сжимает данные по столбцам внутри Compression Units; QUERY LOW/HIGH — для данных с интенсивными запросами, а ARCHIVE LOW/HIGH — для редко используемых архивных данных, обеспечивая очень высокое сжатие ценой высокой нагрузки на CPU."],"level":"expert"},{"id":"oracle-awr-ash-analysis","title":"Анализ производительности с помощью Oracle AWR и ASH","category":"Oracle","summary":"AWR периодически собирает снимки производительности, а ASH ежесекундно делает выборку активных сессий; вместе они отвечают на вопрос «куда ушло время» в терминах wait events.","points":["По умолчанию AWR делает снимок каждые 60 минут и хранит их 8 дней; отчёт AWR между двумя снимками показывает, какие SQL и wait events потребили DB Time (требуется лицензия Diagnostics Pack).","Всегда начинайте анализ с 'Top 10 Foreground Events by Total Wait Time' и DB Time; смотрите там баланс CPU / I/O / конкуренции, а не теряйтесь в отдельных статистиках.","ASH раз в секунду делает выборку в v$active_session_history и фиксирует кратковременные внезапные проблемы (всплески); он выявляет мгновенные штормы блокировок/latch, которые пропускает AWR, по SQL_ID и блокирующей сессии.","Average Active Sessions (AAS = DB Time / прошедшее время) — сводка нагрузки системы одним числом; AAS выше числа ядер CPU указывает на насыщение.","Для настройки SQL возьмите самый дорогой SQL_ID из раздела 'SQL ordered by Elapsed Time/Gets' отчёта AWR, подтвердите через ASH, на каком шаге этот SQL ожидает, затем разберите план построчно с помощью отчёта SQL Monitor."],"level":"expert"},{"id":"oracle-scn-read-consistency","title":"Oracle SCN и согласованность чтения","category":"Oracle","summary":"System Change Number (SCN) — логические часы базы данных; Oracle изолирует читателей от писателей, строя согласованный образ (read consistency) из undo на момент SCN начала запроса.","points":["SCN монотонно возрастает с каждым commit и записывается в redo/undo, управляющий файл и заголовки блоков; если SCN последнего изменения блока больше SCN запроса, блок «откатывается».","Для согласованности чтения Oracle создаёт CR-копию (Consistent Read) текущего блока и откатывает её с помощью undo до SCN начала запроса; поэтому «читатели не блокируют писателей, а писатели не блокируют читателей».","Если undo недостаточно или превышен undo_retention, длительный запрос получает 'ORA-01555 snapshot too old'; решение — увеличить табличное пространство undo, настроить retention или сократить запрос.","Согласованность на уровне оператора использует отдельный SCN для каждого SQL-оператора; транзакция SERIALIZABLE или read-only сохраняет единый SCN с начала транзакции для всех операторов.","В RAC SCN синхронизируется между всеми экземплярами (Lamport или broadcast-on-commit); продвижение SCN координируется между узлами для глобальной согласованности."],"level":"expert"},{"id":"postgres-patroni-failover","title":"PostgreSQL: автоматическая отработка отказа с Patroni","category":"PostgreSQL","summary":"Patroni обеспечивает выбор лидера и автоматическую отработку отказа, храня состояние кластера PostgreSQL в распределённом хранилище консенсуса (DCS); от split-brain защищает watchdog.","points":["Состояние кластера и блокировка лидера хранятся в DCS, например etcd/Consul/ZooKeeper; лидер периодически продлевает блокировку с TTL, и если продлить не удаётся, блокировка истекает и начинаются новые выборы лидера.","При отработке отказа Patroni выбирает наиболее актуальную реплику (с наибольшим LSN); новый лидер повышается (promote), а остальные реплики переподключаются к нему (reinitialize/rewind).","Watchdog (softdog) — страховка на аппаратном уровне, которая перезагружает машину, когда старый лидер теряет связь с DCS, не давая двум узлам одновременно быть лидером (split-brain).","maximum_lag_on_failover не позволяет сильно отставшей реплике стать лидером и вызвать потерю данных; synchronous_mode позволяет достичь RPO=0, но задержка записи растёт.","Клиенты должны подключаться не напрямую к узлу, а через HAProxy с REST health check Patroni или через VIP; так при смене лидера трафик автоматически направляется на новый лидер.","Большинство DCS (кворум) обязательно; используйте кластер etcd из 3 или 5 членов. Если DCS недоступен, Patroni перестраховывается и может перевести текущего лидера в режим только для чтения."],"level":"expert"},{"id":"postgres-logical-replication-upgrade","title":"PostgreSQL: мажорное обновление без простоя с помощью логической репликации","category":"PostgreSQL","summary":"Поскольку логическая репликация может переносить изменения строк между версиями, она позволяет выполнять мажорные обновления почти без простоя; старая и новая версии некоторое время работают параллельно.","points":["Логическая репликация декодирует изменения из WAL на логическом уровне (INSERT/UPDATE/DELETE); в отличие от физической репликации, она работает между разными мажорными версиями и разными платформами.","Порядок: создайте пустую схему в новой версии, определите PUBLICATION в старой версии и SUBSCRIPTION в новой; сначала копируются таблицы (initial sync), затем начинают поступать текущие изменения.","У каждой таблицы должна быть REPLICA IDENTITY; для таблиц без первичного ключа репликация UPDATE/DELETE требует REPLICA IDENTITY FULL, иначе возникает ошибка.","Изменения DDL не реплицируются автоматически, а текущие значения последовательностей не переносятся; при переключении вручную продвиньте последовательности в новой версии, иначе возникнут конфликты ключей.","При переключении ненадолго остановите запись приложения в старую версию и, когда отставание станет нулевым (проверьте через pg_stat_replication), направьте приложение на новую версию; при необходимости держите наготове обратную подписку для быстрого отката.","Ограничения: large objects и часть DDL не реплицируются; необходимо задать wal_level=logical и достаточные max_replication_slots/max_wal_senders."],"level":"expert"},{"id":"postgres-pgvector-similarity","title":"PostgreSQL: поиск по сходству с pgvector","category":"PostgreSQL","summary":"pgvector хранит векторные эмбеддинги и выполняет приближённый поиск ближайших соседей (ANN); индексы HNSW и IVFFlat определяют компромисс между точностью, скоростью и памятью.","points":["Выбирайте оператор расстояния по задаче: <-> L2 (евклидово), <=> косинусное, <#> скалярное произведение; индекс должен использовать тот же класс операторов, что и запрос, иначе он не используется.","HNSW строит многоуровневый граф; даёт высокий recall и быстрые запросы, но требует больше памяти и строится медленно. m (число связей) и ef_construction настраивают качество построения, ef_search — точность во время запроса.","IVFFlat разбивает векторы на lists кластеров и при каждом запросе сканирует только probes кластеров; использует меньше памяти, но индекс нужно создавать ПОСЛЕ загрузки данных (на пустой таблице кластеры бессмысленны).","Для IVFFlat начинайте с lists ≈ rows/1000 до 1M строк и ≈ sqrt(rows) свыше 1M; увеличение probes повышает recall, но замедляет запрос, поэтому настраивайте этот компромисс по измерениям.","ANN возвращает приближённые результаты; если нужна 100% точность, требуется точное сканирование без индекса. При поиске с фильтрами (WHERE) recall индекса может падать; при необходимости рассмотрите iterative scan или частичный индекс.","Размерность векторов и число строк определяют потребность в памяти; во время построения держите maintenance_work_mem достаточно большим, иначе построение HNSW уходит на диск и становится очень медленным."],"level":"expert"},{"id":"postgres-citus-distributed","title":"PostgreSQL: распределённое масштабирование с Citus","category":"PostgreSQL","summary":"Citus масштабирует PostgreSQL горизонтально, разбивая таблицы на шарды по столбцу распределения и распределяя их по worker-узлам; выбор столбца распределения определяет производительность.","points":["Узел coordinator принимает запрос и направляет его в нужные шарды по столбцу распределения; worker-узлы хранят шарды и обрабатывают их параллельно. Шарды — обычные таблицы PostgreSQL (разбиение по диапазонам хеша).","Столбец распределения должен быть общим фильтром большинства запросов и JOIN (например, tenant_id); строки с одинаковым значением попадают в один шард, поэтому JOIN и внешние ключи остаются локальными в пределах одного узла (colocation).","Colocation критически важна: соединяемые таблицы работают без перетасовки данных между узлами (repartition), если они распределены по одному столбцу распределения с одинаковым числом шардов.","Небольшие таблицы, которые часто соединяются со всеми шардами, сделайте reference tables; они полностью копируются на каждый worker и устраняют сетевые затраты в распределённых JOIN.","Запросы без столбца распределения расходятся по всем шардам и дороги; в multi-tenant SaaS фильтрация по tenant_id сводит запрос к одному шарду (router execution)."],"level":"expert"},{"id":"postgres-timescaledb-hypertable","title":"PostgreSQL: гипертаблицы TimescaleDB и continuous aggregates","category":"PostgreSQL","summary":"TimescaleDB масштабирует данные временных рядов с помощью гипертаблиц, автоматически разбивающих их на чанки по интервалам времени, и предвычисленных continuous aggregates.","points":["Гипертаблица снаружи выглядит как одна таблица, но внутри разбита на чанки по столбцу времени (и опционально по пространственному столбцу); запросы затрагивают только чанки нужного временного диапазона (chunk exclusion).","Задавайте chunk_time_interval так, чтобы самые активные чанки помещались в память (shared_buffers); слишком малый интервал даёт избыточные накладные расходы на чанки и планирование, слишком большой — слабое исключение.","Continuous aggregate — инкрементально обновляемое материализованное представление; оно поддерживается в актуальном состоянии в фоне политикой обновления, которая пересчитывает только изменившиеся временные интервалы (buckets).","При real-time aggregation continuous aggregate объединяет материализованные старые данные с последними данными из исходной таблицы, возвращая актуальный результат в момент запроса.","Старые чанки можно существенно уменьшить колоночным сжатием; политика хранения (retention) автоматически удаляет чанки старше заданного возраста с помощью drop_chunks (намного дешевле построчного DELETE)."],"level":"expert"},{"id":"postgres-vacuum-xid-wraparound","title":"PostgreSQL: VACUUM и XID wraparound","category":"PostgreSQL","summary":"MVCC оставляет мёртвые версии строк; VACUUM очищает их и помечает строки как «замороженные» (frozen), предотвращая потерю данных из-за переполнения (wraparound) 32-битного идентификатора транзакции (XID).","points":["PostgreSQL использует 32-битные XID, которые циклически повторяются; каждая версия строки помечается xmin. Если достаточно старые строки не заморожены, при XID wraparound «прошлые» транзакции оказываются в «будущем», и строки могут внезапно стать невидимыми.","autovacuum выполняет две задачи: регулярную очистку (мёртвые кортежи, bloat) и заморозку; для таблиц, приближающихся к autovacuum_freeze_max_age, запускается обязательный «anti-wraparound» vacuum, даже если autovacuum отключён.","По мере приближения wraparound в журнал пишутся предупреждения; если предел очень близок, база данных отказывает в новых записях и переходит в режим только для чтения, чтобы предотвратить потерю данных, а решение — выполнить агрессивный VACUUM (FREEZE).","Отслеживайте самый старый незамороженный XID через pg_database.datfrozenxid и age(); длительные открытые транзакции (idle in transaction) и неиспользуемые слоты репликации блокируют продвижение VACUUM, увеличивая bloat и риск wraparound.","При высокой нагрузке на запись отставание autovacuum ведёт к bloat; повысьте autovacuum_vacuum_cost_limit, уменьшите naptime и примените настройки autovacuum на уровне таблиц к большим/горячим таблицам.","VACUUM обычно не возвращает место операционной системе; он помечает его для повторного использования. Физическое сжатие требует VACUUM FULL (блокирует таблицу) или pg_repack (онлайн)."],"level":"expert"},{"id":"mysql-innodb-cluster","title":"MySQL: Group Replication и InnoDB Cluster","category":"MySQL","summary":"InnoDB Cluster — кластер на основе большинства (кворума), построенный на Group Replication, который обеспечивает высокую доступность, предоставляя приложению единую точку подключения через MySQL Router.","points":["Group Replication распространяет транзакции в группе через уровень групповой коммуникации на основе Paxos; транзакция фиксируется только после согласия большинства группы (кворума), что обеспечивает автоматическую отработку отказа и согласованность.","Режим single-primary (по умолчанию) разрешает запись только на одном узле, и при сбое primary группа автоматически выбирает новый; в режиме multi-primary запись принимают все узлы, но обработка конфликтов (certification) усложняется.","MySQL Router читает метаданные кластера и предоставляет приложению два порта: запись идёт на текущий primary (6446), чтение — на реплики (6447); при отработке отказа маршрутизация обновляется без изменения подключения приложения.","Потеря большинства (например, 2 из 3 узлов недоступны) блокирует группу; для предотвращения split-brain кластер не принимает новых записей, а восстановление выполняется вручную через group_replication_force_members.","Group Replication требует построчного binlog (формат ROW), GTID и InnoDB; первичный ключ в каждой таблице обязателен, иначе узел не сможет присоединиться к кластеру.","Новый узел сначала синхронизируется через передачу состояния с существующего донора (distributed recovery: clone plugin или binlog), затем переключается на живой поток."],"level":"expert"},{"id":"mysql-semisync-replication","title":"MySQL: полусинхронная репликация","category":"MySQL","summary":"Полусинхронная репликация снижает потерю данных: перед подтверждением commit primary ждёт, пока хотя бы одна реплика не получит транзакцию в свой relay log.","points":["При асинхронной репликации primary подтверждает commit, не дожидаясь реплики; при сбое primary ещё не переданные транзакции теряются. Semi-sync ждёт подтверждения получения минимум от N реплик (rpl_semi_sync_source_wait_for_replica_count).","AFTER_SYNC (по умолчанию, рекомендуется): primary сначала получает подтверждение реплики, затем возвращает commit клиенту; это гарантирует, что каждая транзакция, которую клиент видит как «успешную», есть хотя бы на одной реплике.","В режиме AFTER_COMMIT primary сначала фиксирует транзакцию локально, а затем ждёт реплику; если реплика отстаёт, другие сессии могут увидеть ещё не реплицированные данные (похоже на phantom read).","При превышении rpl_semi_sync_source_timeout primary автоматически переходит в асинхронный режим и не останавливается; когда реплика возвращается, он возвращается к semi-sync, поэтому «гарантия» условна.","Semi-sync гарантирует «получено», а не «применено на реплике»; для строгого RPO=0 лучше подходят Group Replication или внешний уровень консенсуса."],"level":"expert"},{"id":"mysql-undo-purge","title":"MySQL InnoDB: undo tablespaces и purge","category":"MySQL","summary":"InnoDB хранит старые версии строк в undo log для MVCC; потоки purge очищают записи undo и мёртвые версии, которые больше не нужны ни одному читателю.","points":["Каждое изменение создаёт запись undo log для отката и согласованного чтения; длительная транзакция удерживает старые read views, блокируя продвижение purge и вызывая рост undo (растёт history list length).","Если history list length (SHOW ENGINE INNODB STATUS) продолжает расти, purge не успевает; основная причина обычно — длительные открытые транзакции или удерживаемые старые чтения (например, mysqldump --single-transaction).","innodb_purge_threads управляет параллелизмом purge, а innodb_max_purge_lag добавляет искусственную задержку к DML, когда purge сильно отстаёт, не давая нагрузке перегрузить purge.","В MySQL 8 undo хранится в отдельных undo tablespaces, и разросшиеся файлы undo можно уменьшать автоматическим усечением (innodb_undo_log_truncate); в старых версиях undo находился в системном табличном пространстве и никогда не уменьшался.","Разросшийся undo заставляет согласованное чтение проходить более длинные цепочки версий (I/O и CPU); короткие транзакции и дисциплина autocommit минимизируют нагрузку на undo."],"level":"expert"},{"id":"mysql-buffer-pool-lru","title":"MySQL InnoDB: экземпляры buffer pool и LRU","category":"MySQL","summary":"InnoDB buffer pool хранит страницы данных и индексов в памяти; алгоритм LRU со вставкой в середину (midpoint insertion) не даёт сканированиям таблиц вытеснять горячие данные из памяти.","points":["На выделенном сервере БД buffer pool обычно задают ~70-80% RAM (innodb_buffer_pool_size; на общем сервере меньше); цель — держать часто используемый рабочий набор в памяти и минимизировать дисковый I/O.","Список LRU делится на подсписки «young» (горячий) и «old» (холодный); новая прочитанная страница вставляется не в начало списка, а в начало old-области (midpoint, ~37%), поэтому разовое сканирование не вытесняет горячие страницы.","Страница в old-области переводится в young, только если к ней снова обращаются после истечения innodb_old_blocks_time; это не даёт большим полным сканированиям засорять buffer pool.","innodb_buffer_pool_instances разделяет пул на несколько независимых экземпляров; поскольку у каждого экземпляра свой mutex, конкуренция за mutex buffer pool при высокой параллельности снижается (имеет смысл для больших пулов).","Отслеживайте hit ratio buffer pool и баланс свободных/грязных страниц; большое число грязных страниц и агрессивный сброс могут вызвать «шторм контрольных точек», а innodb_io_capacity задаёт скорость сброса в соответствии с хранилищем."],"level":"expert"},{"id":"sqlserver-always-on-ag","title":"SQL Server: Always On Availability Groups","category":"SQL Server","summary":"Availability Group реплицирует группу баз данных на синхронные/асинхронные реплики; на WSFC обеспечивает автоматическую отработку отказа, читаемые реплики и read-only routing.","points":["С репликой synchronous-commit primary перед commit ждёт, пока secondary сохранит (harden) запись журнала; это даёт RPO=0, но при отставании реплики увеличивает задержку commit на primary.","Реплика asynchronous-commit не заставляет primary ждать; она подходит для удалённых площадок аварийного восстановления (DR), но при отработке отказа возможна потеря части данных (RPO>0), и поддерживается только принудительная ручная отработка отказа (FORCE_FAILOVER_ALLOW_DATA_LOSS).","Автоматическая отработка отказа происходит только между парой реплик в режиме synchronous-commit и «automatic failover» при сохранении кворума WSFC; проверки работоспособности выполняются через sp_server_diagnostics и database health detection.","Читаемая secondary-реплика снимает с primary нагрузку отчётов; благодаря read-only routing клиенты, подключающиеся с ApplicationIntent=ReadOnly, автоматически направляются через listener на читаемую реплику.","Запросы на читаемой реплике автоматически используют snapshot isolation; для этого к изменённым строкам на primary добавляется 14-байтовая метка версии, а на secondary нагружается version store в tempdb; кроме того, при отставании redo apply данные на реплике могут устареть.","AG listener предоставляет клиентам единую точку подключения с виртуальным сетевым именем + IP; при отработке отказа listener переходит на новый primary, и приложение продолжает работать без изменения строки подключения."],"level":"expert"},{"id":"sqlserver-columnstore","title":"SQL Server: columnstore-индексы и batch mode","category":"SQL Server","summary":"Columnstore-индекс хранит данные по столбцам с высоким сжатием; batch mode processing и segment elimination дают значительное ускорение аналитических запросов.","points":["Данные разбиваются на сегменты столбцов в rowgroups (~1 миллион строк), и каждый сегмент сжимается; читаются только нужные запросу столбцы, что значительно снижает I/O.","Каждый сегмент содержит метаданные min/max; если предикат запроса вне диапазона значений сегмента, сегмент пропускается без чтения (segment elimination), поэтому загрузка данных в отсортированном порядке очень важна.","Batch mode обрабатывает строки группами по ~900 вместо построчной обработки, увеличивая объём работы на такт CPU; для агрегатов и соединений он многократно быстрее row mode (с SQL 2019+ batch mode возможен и на rowstore).","Clustered columnstore идеален для больших аналитических/DW таблиц фактов; для смешанных нагрузок OLTP + аналитика операционную аналитику в реальном времени (HTAP) можно построить с nonclustered columnstore на rowstore-таблице.","Горячие (ещё не сжатые) строки хранятся построчно в deltastore и сжимаются tuple mover при заполнении; множество мелких загрузок вызывает фрагментацию, поэтому предпочтительна массовая загрузка."],"level":"expert"},{"id":"sqlserver-inmemory-oltp","title":"SQL Server: In-Memory OLTP (Hekaton)","category":"SQL Server","summary":"In-Memory OLTP устраняет узкие места в высококонкурентных OLTP-нагрузках, обрабатывая memory-optimized таблицы структурами без lock/latch и natively compiled процедурами.","points":["Memory-optimized таблицы полностью хранятся в памяти, и все версии строк живут в памяти; конкуренция обрабатывается оптимистичным MVCC без блокировок, что устраняет классическую конкуренцию за latch и блокировки.","Для долговечности изменения по-прежнему пишутся в журнал и сбрасываются в файлы контрольных точек (data/delta); таблицы SCHEMA_ONLY не журналируются и очищаются при перезапуске (идеально, например, для временных staging/сессионных данных).","Natively compiled хранимая процедура компилирует T-SQL в машинный код (DLL), устраняя накладные расходы интерпретации; она может обращаться только к memory-optimized таблицам и поддерживает ограниченное подмножество T-SQL.","Поскольку конкуренция оптимистичная, конфликты обнаруживаются во время выполнения; конфликт write-write откатывает транзакцию с ошибкой (требуется повтор), поэтому в приложении должна быть логика повторов.","Лучшие сценарии: очень высокая скорость вставки (например, приём IoT/телеметрии), горячие таблицы с узкими местами lock/latch и временные таблицы, использующие tempdb; все данные должны помещаться в память, а давление на память нужно внимательно отслеживать."],"level":"expert"},{"id":"sqlserver-query-store","title":"SQL Server: управление планами с Query Store","category":"SQL Server","summary":"Query Store сохраняет тексты запросов, планы выполнения и статистику выполнения; позволяет обнаруживать регрессии планов и принудительно задавать конкретный план.","points":["Query Store хранит внутри базы данных все планы, которые запрос использовал с течением времени, вместе с метриками длительности/CPU/чтений по каждому плану; он отвечает на вопрос «почему вчера этот запрос был быстрым, а сегодня медленный» историческими данными.","Регрессии планов (новый скомпилированный план хуже старого) находят с помощью отчётов 'Regressed Queries' и 'Top Resource Consumers'; стабильность восстанавливается принудительным применением известного хорошего плана через sys.sp_query_store_force_plan.","С Query Store hints в SQL Server 2022 подсказки (например, RECOMPILE, MAXDOP) можно применять без принудительного плана и без изменения текста запроса; это мощный инструмент для настройки запросов сторонних приложений.","Автоматическая коррекция планов (automatic tuning, FORCE_LAST_GOOD_PLAN) обнаруживает регрессии, автоматически принудительно применяет предыдущий хороший план и отменяет это, если улучшения нет; включайте в production под наблюдением.","Правильно задайте режим сбора (ALL/AUTO/CUSTOM) и размер хранилища; AUTO отфильтровывает редкие/незначительные запросы, снижая накладные расходы и объём, а заполненный Query Store переходит в режим только для чтения и прекращает сбор данных."],"level":"expert"},{"id":"mongodb-sharding-deep","title":"MongoDB: шардинг, чанки и балансировщик","category":"MongoDB","summary":"MongoDB разбивает данные на чанки по ключу шардирования и распределяет их по шардам; балансировщик выравнивает чанки, а выбор ключа шардирования определяет распределение и маршрутизацию запросов.","points":["Данные разбиваются на чанки по диапазонам ключа шардирования, и чанки распределяются по шардам; маршрутизатор mongos смотрит на ключ шардирования и направляет запрос только в нужный шард (targeted), иначе рассылает его во все шарды (scatter-gather).","Хороший ключ шардирования имеет высокую кардинальность, низкую частоту и не возрастает монотонно; монотонно возрастающий ключ (например, timestamp/ObjectId) направляет все новые записи в один шард (hotspot), а hashed sharding распределяет их.","Когда число чанков между шардами становится несбалансированным, балансировщик переносит чанки в фоне; согласованность между источником и целью при миграции сохраняется, но это создаёт нагрузку на I/O, и миграцию можно запланировать в окно обслуживания.","Jumbo chunk — чанк, превышающий максимальный размер чанка и не поддающийся разбиению из-за низкой кардинальности ключа шардирования; его нельзя перенести, он создаёт дисбаланс, а решение — более селективный (составной) ключ шардирования.","Zone sharding (tag-aware) закрепляет определённые диапазоны ключа шардирования за определёнными шардами; используется для управления размещением данных по географии или оборудованию (например, данные ЕС на шарде в регионе ЕС).","Хотя в современных версиях вместо числа чанков используется балансировка по размеру и автоматическая дефрагментация, принцип тот же: для targeted-запросов ключ шардирования должен присутствовать в каждом чтении/записи."],"level":"expert"},{"id":"mongodb-oplog-election","title":"MongoDB: oplog набора реплик и выборы","category":"MongoDB","summary":"В наборе реплик primary записывает операции записи в oplog, а secondary применяют их; при сбое primary новый primary выбирается через выборы, похожие на Raft.","points":["Oplog — capped collection, которая записывает каждое изменение на primary в идемпотентной (безопасно применяемой повторно) форме; secondary читают (tail) и применяют oplog, оставаясь синхронизированными.","Размер oplog определяет окно репликации; если secondary находится офлайн дольше, чем покрывает oplog, он становится «stale» и требует полной ресинхронизации (initial sync), поэтому размер oplog нужно подбирать под нагрузку.","При сбое primary secondary голосуют по протоколу, похожему на Raft; кандидат, получивший большинство голосов, становится primary, поэтому для сохранения большинства держат нечётное число голосов (при необходимости с arbiter).","writeConcern 'majority' гарантирует, что запись дошла до большинства узлов и поэтому не будет откачена при отработке отказа (защита от rollback); w:1 быстрее, но запись может быть потеряна при сбое primary.","readConcern и read preference управляют актуальностью чтения; чтение с secondary распределяет нагрузку, но может вернуть устаревшие данные из-за отставания репликации, тогда как 'majority'/'linearizable' дают более сильные гарантии.","Если после сетевого разделения старый primary оказывается в меньшинстве, он понижается до secondary; при повторном присоединении к новому primary он переносит свои незафиксированные записи в rollback-файл (majority write concern это предотвращает)."],"level":"expert"},{"id":"mongodb-wiredtiger-checkpoint","title":"MongoDB: контрольные точки WiredTiger и журнал","category":"MongoDB","summary":"WiredTiger обеспечивает долговечность через периодические контрольные точки и журнал упреждающей записи; он даёт высокую пропускную способность благодаря конкуренции на уровне документов и сжатию.","points":["По умолчанию WiredTiger делает контрольную точку примерно каждые 60 секунд, записывая согласованный снимок из памяти на диск; между контрольными точками долговечность обеспечивается записью каждой операции в журнал (WAL).","Без журнала при сбое теряются записи, сделанные после последней контрольной точки; с журналом восстановление загружает последнюю контрольную точку и повторяет последующие операции из журнала.","Управление конкуренцией на уровне документов позволяет одновременно обновлять разные документы одной коллекции; снимки MVCC обеспечивают согласованное чтение.","Кеш WiredTiger (по умолчанию ~50% RAM) хранит часто используемые данные и индексы; если рабочий набор не помещается в кеш, растут eviction и дисковый I/O, а насыщение кеша сильно снижает производительность.","Сжатие хранилища по умолчанию — snappy (баланс скорости и степени сжатия); zstd/zlib дают более высокую степень сжатия, но требуют больше CPU. Индексы дополнительно уменьшаются с помощью prefix compression."],"level":"expert"},{"id":"redis-cluster-hashslot","title":"Redis Cluster: hash slots и решардинг","category":"Redis","summary":"Redis Cluster масштабируется горизонтально, разделяя пространство ключей на 16384 hash slots и распределяя их по узлам; клиенты находят нужный узел через перенаправления MOVED/ASK.","points":["Каждый ключ сопоставляется слоту по формуле CRC16(key) mod 16384; каждый master владеет определёнными диапазонами слотов, поэтому кластер распределяет данные без центрального маршрутизатора.","Если клиент обращается не к тому узлу, узел отвечает MOVED, указывая постоянного владельца слота, и клиент обновляет свою карту слотов. Во время решардинга для переносимого слота используется временное перенаправление ASK.","Решардинг переносит слоты (и ключи в них) с одного узла на другой в онлайн-режиме; миграция выполняется по ключам, и кластер продолжает обслуживать запросы всё это время.","Многоключевые операции (MSET, транзакции, Lua) работают только с ключами из одного слота; hash tags ({...}) используются для группировки связанных ключей в один слот, например user:{123}:name и user:{123}:email.","Для высокой доступности дайте каждому master хотя бы одну реплику; при сбое master его реплика повышается. Кластер принимает решения большинством master, и при потере большинства запись прекращается.","Если какой-либо слот не назначен или не имеет master, кластер отклоняет все записи (когда включён cluster-require-full-coverage); все 16384 слота должны быть назначены."],"level":"expert"},{"id":"redis-replication-psync","title":"Redis: репликация и PSYNC","category":"Redis","summary":"При асинхронной репликации Redis master передаёт реплике снимок данных, а затем транслирует последующие команды; PSYNC выполняет частичную ресинхронизацию после коротких разрывов.","points":["При первичной синхронизации master создаёт снимок RDB и отправляет его реплике (full resync); тем временем он буферизует входящие записи в replication backlog buffer и передаёт их после RDB.","PSYNC отслеживает replication offset реплики и master replication ID; после короткого разрыва, если пропущенные данные ещё в backlog buffer, отправляется только недостающая часть (partial resync), что позволяет избежать дорогой полной ресинхронизации.","repl-backlog-size определяет, какой длительности разрыв можно пережить; при высокой скорости записи маленький backlog приводит к полной ресинхронизации при разрывах, а это генерация RDB на master и сетевые затраты.","Репликация асинхронна: master подтверждает запись, не дожидаясь реплик, поэтому при внезапном сбое master последние записи могут быть потеряны; командой WAIT можно дождаться получения заданным числом реплик, но это не настоящая синхронная гарантия.","При бездисковой репликации (repl-diskless-sync) master может передавать RDB напрямую в сокет, не записывая его на диск; это ускоряет первичную синхронизацию в средах с медленными дисками, но быстрой сетью."],"level":"expert"},{"id":"redis-memory-fragmentation","title":"Redis: фрагментация памяти и jemalloc","category":"Redis","summary":"Redis по умолчанию использует jemalloc; выделения и освобождения памяти переменного размера со временем вызывают фрагментацию, из-за чего RSS выглядит больше, чем реально используемые данные.","points":["mem_fragmentation_ratio = used_memory_rss / used_memory; значение около 1 идеально, заметно выше 1 (например, >1.5) указывает на фрагментацию, а ниже 1 — на вытеснение данных в swap (плохо).","Фрагментация возникает, когда ключи разного размера постоянно создаются и удаляются, оставляя непригодные промежутки в аренах jemalloc; частые изменения больших значений усугубляют её.","Активная дефрагментация (activedefrag yes) уплотняет блоки памяти во время работы с помощью jemalloc, снижая фрагментацию; её пороги (active-defrag-ignore-bytes, -threshold-lower/upper) настраивают, чтобы ограничить накладные расходы.","Правильно задайте maxmemory и maxmemory-policy; поскольку реальный RSS, выделенный jemalloc, может превысить maxmemory, держите maxmemory ниже объёма RAM, чтобы избежать OOM операционной системы.","Если used_memory низкий, а RSS остаётся высоким, перезапуск (или узел, перезапущенный через failover) обычно надёжно сбрасывает фрагментацию; active defrag в большинстве случаев снижает потребность в перезапусках."],"level":"expert"},{"id":"elasticsearch-shard-sizing","title":"Elasticsearch: определение размера шардов","category":"Elasticsearch","summary":"Каждый шард — это индекс Lucene с фиксированной стоимостью ресурсов; слишком много мелких шардов (oversharding) или слишком большие шарды ухудшают производительность и стабильность.","points":["Практическое правило: держите шард в диапазоне 10-50 GB; очень большие шарды замедляют восстановление и ребалансировку, а очень мелкие (oversharding) создают лишнюю нагрузку на cluster state и heap.","Каждый шард, и особенно каждое поле в mapping, потребляет heap. Старое правило «~20 шардов на GB heap» с версии 8.3 больше не действует (накладные расходы heap на шард резко снизились); актуальная рекомендация — закладывать ~1 KB heap на каждое поле mapping на индекс на data-узлах плюс накладные расходы и держать число шардов на узел ниже cluster.max_shards_per_node (по умолчанию 1000). Слишком много шардов по-прежнему раздувает cluster state и увеличивает накладные расходы на координацию.","Задержка запросов связана с числом шардов: запрос расходится по всем нужным шардам, и результаты объединяются; лишние шарды замедляют даже небольшие запросы из-за накладных расходов на координацию.","Для временных рядов/логов используйте индексы по времени (data streams) вместо одного гигантского индекса; это делает перемещение/удаление старых данных по индексам (ILM) дешёвым и сохраняет шарды управляемыми.","Число primary-шардов фиксируется при создании индекса и позже не меняется (только через reindex/split/shrink); поэтому планируйте его заранее по оценкам роста, тогда как число реплик можно менять динамически."],"level":"expert"},{"id":"elasticsearch-ilm-tiers","title":"Elasticsearch: ILM и уровни hot-warm-cold-frozen","category":"Elasticsearch","summary":"Index Lifecycle Management экономично управляет данными временных рядов, автоматически перемещая индексы между уровнями стоимости/производительности в зависимости от фазы жизненного цикла.","points":["Политика ILM проводит индекс через фазы: hot (активная запись + частые запросы, быстрые SSD), warm (запись завершена, редкие запросы), cold (очень редкие запросы, более дешёвое оборудование), frozen (крайне редко, обслуживание из объектного хранилища через searchable snapshots).","Rollover открывает новый индекс, когда hot-индекс достигает заданного размера/возраста/числа документов; так один индекс не растёт бесконечно, и каждая фаза управляется на уровне индекса.","Уровень frozen хранит данные в объектном хранилище (S3/blob) через searchable snapshots и частично подгружает их в память только при необходимости; это сильно снижает стоимость дисков, но задержка запросов растёт.","При переходе в warm/cold хранилище и heap оптимизируются с помощью force merge, shrink и уменьшения числа реплик; поскольку в этих фазах данные больше не меняются, сегменты можно слить до одного.","Сопоставьте уровни данных с ролями узлов (data_hot/data_warm/data_cold/data_frozen); ILM использует фильтры shard allocation, чтобы размещать индекс на узлах с подходящим оборудованием."],"level":"expert"},{"id":"elasticsearch-segment-merge","title":"Elasticsearch: слияние сегментов и force merge","category":"Elasticsearch","summary":"Lucene при каждом refresh создаёт новые неизменяемые сегменты; фоновое слияние объединяет и удаляет сегменты, а force merge — ручная операция, требующая осторожности.","points":["Каждый refresh (по умолчанию 1 s) создаёт новый сегмент, а удаления помечаются в сегменте как «tombstones»; множество мелких сегментов замедляет чтение, поэтому фоновое слияние объединяет их.","Удалённые/обновлённые документы физически удаляются с диска только при слиянии; при интенсивных обновлениях в сегментах накапливаются «deleted docs», и использование диска/памяти растёт.","Для индексов, в которые больше не пишут (read-only), force merge до 1 сегмента повышает скорость запросов и эффективность использования диска; это типичный шаг фазы ILM warm.","НИКОГДА не выполняйте force merge для индекса с активной записью; созданный очень большой сегмент не попадает в обычный цикл слияния, последующие удаления не очищаются, и он надолго перегружает I/O и heap.","Временное увеличение refresh_interval при массовой загрузке (например, -1 или 30s) создаёт меньше сегментов большего размера, снижая нагрузку слияния и повышая пропускную способность записи."],"level":"expert"},{"id":"elasticsearch-node-roles","title":"Elasticsearch: роли узлов и топология кластера","category":"Elasticsearch","summary":"Узлы Elasticsearch выполняют роли master, data, coordinating и ingest; разделение ролей в больших кластерах обеспечивает стабильность и масштабируемость.","points":["Узлы master-eligible управляют cluster state (индексы, размещение шардов, mappings); для стабильных выборов используйте нечётное число (обычно 3) выделенных master-узлов и опирайтесь на кворум (большинство) для предотвращения split-brain.","Data-узлы хранят шарды и обрабатывают I/O индексирования и запросов; они потребляют больше всего CPU/RAM/диска и могут разделяться на уровни данных (hot/warm/cold/frozen).","Узел coordinating-only не имеет других ролей, распределяет запросы по нужным шардам и объединяет результаты (scatter-gather); он защищает data-узлы, беря на себя тяжёлое объединение результатов поиска/агрегаций, и выступает балансировщиком нагрузки.","Ingest-узел обрабатывает данные через pipelines (преобразование, обогащение) перед индексированием; вынос тяжёлых ingest pipelines на выделенные ingest-узлы защищает производительность запросов на data-узлах.","Никогда не нагружайте master-узлы тяжёлыми запросами/данными; замедление master под нагрузкой задерживает обновления cluster state и вызывает нестабильность всего кластера (задержка размещения шардов, выборы)."],"level":"expert"}]},"guide/glossary.json":{"terms":[{"term":"ACID","full":"Atomicity, Consistency, Isolation, Durability","definition":"Четыре базовых свойства, делающих транзакции надёжными: атомарность (всё или ничего), согласованность (правила соблюдаются), изоляция (параллельные транзакции не портят друг друга) и долговечность (зафиксированные данные не теряются). Это гарантия транзакций реляционных баз данных.","tags":["transaction","transaction","acid","consistency","consistency"]},{"term":"BASE","full":"Basically Available, Soft state, Eventually consistent","definition":"Ослабленный подход к согласованности, альтернатива ACID, распространённый в распределённых и NoSQL-системах. Система ставит в приоритет постоянную доступность и допускает, что данные становятся согласованными со временем (eventual consistency). Строгая согласованность меняется на масштабируемость.","tags":["base","distributed","distributed","eventual-consistency","nosql"]},{"term":"MVCC","full":"Multi-Version Concurrency Control","definition":"Метод управления конкуренцией, при котором хранится несколько версий одной строки, чтобы читатели и писатели работали, не блокируя друг друга. Каждая транзакция видит собственный снимок данных, поэтому чтение не блокирует запись, а запись не блокирует чтение. Используется в таких системах, как PostgreSQL и Oracle.","tags":["concurrency","concurrency","lock","mvcc","snapshot"]},{"term":"WAL","full":"Write-Ahead Log","definition":"Принцип, по которому запись журнала надёжно сохраняется на диск ДО записи изменения в файлы данных. На нём основаны восстановление после сбоя и долговечность. В PostgreSQL называется WAL, в SQL Server — transaction log.","tags":["wal","log","recovery","recovery","durability"]},{"term":"Redo Log","full":"","definition":"Журнал, который фиксирует изменения в базе данных и используется для повторного применения (redo) работы, потерянной при сбое. При восстановлении он накатывает изменения, которые были зафиксированы, но ещё не записаны на диск. Redo log в Oracle соответствует общему понятию WAL.","tags":["redo","log","recovery","recovery","oracle"]},{"term":"Undo / Rollback Segment","full":"","definition":"Область, хранящая предыдущий образ данных, изменённых транзакцией. Служит двум целям: отмене изменения при rollback и выдаче старой версии читателям в рамках MVCC. В Oracle называется undo/rollback segment, в других системах — undo/version store.","tags":["undo","rollback","rollback","mvcc","segment"]},{"term":"Buffer Pool / Buffer Cache","full":"","definition":"Кеш в памяти, где хранятся часто используемые страницы данных и индексов. Поскольку чтение с диска дорого, производительность базы данных сильно зависит от доли попаданий в кеш (cache hit ratio). В MySQL InnoDB называется buffer pool, в SQL Server — buffer cache.","tags":["buffer-pool","buffer-cache","cache","cache","memory"]},{"term":"Dirty Page","full":"","definition":"Страница данных, изменённая в памяти, но ещё не записанная на диск. Долговечность обеспечивает WAL; сама страница остаётся грязной, пока checkpoint или background writer не сбросит её на диск. Слишком большое число грязных страниц удлиняет время восстановления.","tags":["dirty-page","dirty-page","memory","checkpoint","io"]},{"term":"LSN","full":"Log Sequence Number","definition":"Постоянно возрастающий уникальный порядковый номер, присваиваемый каждой записи в WAL/журнале транзакций. Используется, чтобы определить, до какой записи журнала актуальна страница и откуда продолжать восстановление. Также отслеживает прогресс репликации.","tags":["lsn","log","sequence-number","recovery","replication"]},{"term":"SCN","full":"System Change Number","definition":"В Oracle — монотонно возрастающая логическая временная метка, присваиваемая каждой точке commit в базе данных. Используется для согласованности чтения, MVCC и упорядочивания событий при восстановлении. Можно считать аналогом LSN в Oracle.","tags":["scn","oracle","timestamp","read-consistency","commit"]},{"term":"Isolation Level","full":"Isolation Level","definition":"Настройка, определяющая, в какой мере параллельные транзакции видят изменения друг друга. Уровни: Read Uncommitted, Read Committed, Repeatable Read и Serializable; с повышением уровня согласованность растёт, а параллелизм снижается. Управляет аномалиями dirty/non-repeatable/phantom read.","tags":["isolation","isolation-level","transaction","concurrency","concurrency"]},{"term":"Dirty Read","full":"","definition":"Чтение транзакцией изменения, которое другая транзакция ещё не зафиксировала (и может откатить). Поскольку прочитанные данные могут оказаться недействительными, это аномалия согласованности. Возможно только на уровне изоляции Read Uncommitted.","tags":["dirty-read","anomaly","isolation","transaction","consistency"]},{"term":"Non-repeatable Read","full":"","definition":"Получение другого значения при повторном чтении той же строки в рамках одной транзакции, потому что в промежутке другая транзакция выполнила commit. Может возникать на Read Committed; предотвращается на Repeatable Read и выше.","tags":["non-repeatable-read","anomaly","isolation","transaction","non-repeatable"]},{"term":"Phantom Read","full":"","definition":"Изменение набора строк при повторном выполнении запроса с тем же условием в рамках одной транзакции из-за промежуточной вставки/удаления (появляются новые «фантомные» строки). По стандарту SQL предотвращается на Serializable; PostgreSQL предотвращает его и на Repeatable Read.","tags":["phantom-read","phantom","anomaly","serializable","isolation"]},{"term":"Deadlock","full":"","definition":"Ситуация, когда две или более транзакции ждут блокировку, удерживаемую другой, и ни одна не может продолжить. База данных обнаруживает взаимоблокировку и разрывает цикл, обычно выбирая самую «дешёвую» транзакцию в качестве «жертвы» и откатывая её. Захват блокировок всегда в одном порядке снижает риск.","tags":["deadlock","deadlock","lock","lock","concurrency"]},{"term":"Lock Escalation","full":"","definition":"Когда транзакция удерживает множество блокировок строк/страниц, база данных преобразует их в одну более крупную блокировку (например, блокировку таблицы). Это снижает расход памяти на управление блокировками, но уменьшает параллелизм и может усилить блокирование. Встречается особенно в SQL Server.","tags":["lock-escalation","lock","lock","concurrency","sql-server"]},{"term":"Latch","full":"","definition":"Лёгкая, недорогая блокировка для управления конкуренцией, защищающая структуры в памяти (например, страницы buffer pool) на очень короткое время. В отличие от транзакционных блокировок, защищает физическую структуру памяти, а не логические данные, и удерживается лишь очень кратко.","tags":["latch","lock","memory","concurrency","internal"]},{"term":"B-Tree Index","full":"Balanced Tree Index","definition":"Самый распространённый тип индекса, хранящий данные в отсортированной сбалансированной древовидной структуре. Эффективен для запросов на равенство и диапазоны, сортировки и поиска по префиксу. Тип индекса по умолчанию в большинстве баз данных.","tags":["btree","index","index","search","range"]},{"term":"Hash Index","full":"","definition":"Тип индекса, который находит ключ, пропуская его через хеш-функцию. Очень быстр только для точного поиска на равенство (=); не может использоваться для запросов по диапазону и сортировки. Доступен, например, в PostgreSQL и в таблицах MySQL Memory.","tags":["hash-index","index","index","equality","search"]},{"term":"Bitmap Index","full":"","definition":"Индекс, хранящий битовые массивы для каждого уникального значения; подходит для столбцов с низкой кардинальностью (мало уникальных значений). Позволяет быстро комбинировать несколько условий через AND/OR; эффективен для нагрузок хранилищ данных/OLAP, но неэффективен при интенсивной записи.","tags":["bitmap-index","index","index","olap","cardinality"]},{"term":"Покрывающий индекс","full":"","definition":"Индекс, содержащий все столбцы, нужные запросу; запрос выполняется только по индексу, без обращения к таблице (heap/clustered). Это повышает производительность за счёт исключения дорогих обращений к строкам. Может создаваться с INCLUDE-столбцами.","tags":["covering-index","index","index","performance","lookup"]},{"term":"Кластеризованный индекс","full":"","definition":"Индекс, при котором строки таблицы физически упорядочены по ключу индекса; сама таблица хранится в этом порядке. В таблице он может быть только один. Быстр для диапазонных запросов; в InnoDB первичный ключ всегда кластеризованный.","tags":["clustered-index","index","index","physical","primary-key"]},{"term":"Кардинальность","full":"","definition":"Число различных значений в столбце. Высокая кардинальность (например, email) означает много разных значений, низкая (например, пол) — мало. Это критически важная метрика для оптимизатора при выборе индексов и оценке стоимости плана.","tags":["cardinality","cardinality","statistics","optimizer","distinct"]},{"term":"Селективность","full":"","definition":"Показывает, какую малую долю от общего числа строк возвращает условие фильтра. Высокая селективность (мало строк) делает использование индекса выгодным; при низкой селективности (много строк) полное сканирование может оказаться дешевле. Оптимизатор использует её при выборе плана.","tags":["selectivity","selectivity","optimizer","index","statistics"]},{"term":"План выполнения","full":"","definition":"Схема шагов (тип сканирования, метод соединения, сортировка и т. д.), выбранных оптимизатором для выполнения запроса. Анализ с помощью EXPLAIN/EXPLAIN ANALYZE выявляет первопричину проблем производительности. Важно сравнивать оценочную и фактическую стоимость.","tags":["execution-plan","query-plan","explain","optimizer","performance"]},{"term":"Стоимостной оптимизатор","full":"CBO","definition":"Оптимизатор запросов, который по статистике оценивает стоимость возможных планов выполнения и выбирает самый дешёвый. Для правильных решений необходима актуальная статистика; устаревшая статистика приводит к плохим планам. Стандартный подход в современных СУБД.","tags":["cbo","optimizer","cost","statistics","query-plan"]},{"term":"Статистика","full":"Статистика","definition":"Метаданные, собираемые для таблицы/индекса, чтобы оптимизатор мог оценить число строк, кардинальность и распределение значений. Актуальная статистика — это хорошие планы запросов; устаревшую нужно обновлять через ANALYZE/UPDATE STATISTICS. Одна из самых частых причин низкой производительности.","tags":["statistics","statistics","optimizer","analyze","query-plan"]},{"term":"Гистограмма","full":"","definition":"Структура статистики, описывающая распределение значений в столбце путём разбиения их на диапазоны (бакеты). Позволяет оптимизатору точнее оценивать число строк, особенно при неравномерном распределении. Составная часть статистики.","tags":["histogram","statistics","statistics","optimizer","distribution"]},{"term":"Секция (партиция)","full":"","definition":"Метод, при котором большая таблица физически разбивается на меньшие части (например, по дате или диапазону), оставаясь логически единой таблицей. Запросы сканируют только нужную часть (partition pruning), а обслуживание и архивирование упрощаются. Отличие от шардинга: всё остаётся в пределах одного сервера/экземпляра.","tags":["partition","partitioning","scaling","pruning","big-data"]},{"term":"Шардинг","full":"","definition":"Метод горизонтального распределения данных по нескольким отдельным серверам/базам данных в соответствии с ключом шардирования. Обеспечивает горизонтальное масштабирование за пределы одной машины, но усложняет управление запросами и транзакциями. Отличие от секционирования: данные распределены по разным узлам.","tags":["sharding","scaling","distributed","distributed","horizontal-scaling"]},{"term":"Репликация (синхронная/асинхронная)","full":"","definition":"Копирование данных с основного узла на одну или несколько копий (реплик). При синхронной репликации commit ждёт подтверждения от реплики (без потери данных, выше задержка); при асинхронной — не ждёт (быстро, но при отставании реплики возможна потеря данных). Используется для HA и масштабирования чтения.","tags":["replication","replication","synchronous","asynchronous","ha"]},{"term":"Логическая и физическая репликация","full":"","definition":"Физическая репликация создаёт точную копию на уровне дисковых блоков/WAL (та же версия, вся база данных). Логическая репликация передаёт изменения на уровне строк/SQL; она позволяет выборочно реплицировать таблицы и передавать данные между версиями и разными схемами. Логическая репликация предпочтительна для CDC и сценариев обновления версии.","tags":["logical-replication","physical-replication","replication","cdc","wal"]},{"term":"Failover","full":"","definition":"Автоматический или ручной перевод обслуживания на резервный узел при отказе основного. Минимизирует время простоя; RPO определяет, сколько данных может быть потеряно, а RTO — сколько длится восстановление. Применяется при незапланированных сбоях.","tags":["failover","ha","high-availability","standby","rpo"]},{"term":"Switchover","full":"","definition":"Плановая контролируемая смена ролей основного и резервного узлов (например, для обслуживания). Отличие от failover: это плановая операция, а не сбой, и обычно выполняется без потери данных.","tags":["switchover","planned","ha","maintenance","standby"]},{"term":"RPO","full":"Recovery Point Objective","definition":"Максимально допустимый объём потери данных после аварии (выражается во времени). Например, при RPO 5 минут допускается потеря данных не более чем за 5 минут. Частота бэкапов и тип репликации выбираются под эту цель.","tags":["rpo","data-loss","dr","disaster-recovery","backup"]},{"term":"RTO","full":"Recovery Time Objective","definition":"Максимальное целевое время возврата системы в рабочее состояние после простоя. Например, при RTO 1 час сервис должен быть восстановлен не позднее чем через 1 час. Архитектура HA/DR и уровень автоматизации проектируются под эту цель.","tags":["rto","recovery-time","dr","disaster-recovery","ha"]},{"term":"HA","full":"Высокая доступность","definition":"Цель проектирования — обеспечить работу системы без перерывов или с минимальными перерывами несмотря на сбои. Достигается за счёт резервирования, автоматической отработки отказа и проверок работоспособности. Обычно выражается в процентах времени доступности (например, 99.99%).","tags":["ha","high-availability","high-availability","failover","redundancy"]},{"term":"DR","full":"Аварийное восстановление","definition":"Планы и процессы восстановления систем и данных, обычно в географически удалённом регионе, после крупных аварий — пожара, наводнения или отказа целого региона. Измеряется целевыми RPO и RTO. HA ориентирована на повседневные сбои, DR — на крупные аварии.","tags":["dr","disaster-recovery","disaster-recovery","rpo","rto"]},{"term":"Кворум","full":"","definition":"Минимальное большинство узлов (обычно N/2 + 1), необходимое для того, чтобы решение (например, выбор лидера или подтверждение записи) в распределённой системе считалось действительным. Используется для предотвращения split-brain и сохранения согласованности. Поэтому размер кластера чаще всего нечётный.","tags":["quorum","majority","distributed","distributed","split-brain"]},{"term":"Split-Brain","full":"","definition":"Ситуация, когда после сетевого разделения обе половины кластера считают себя основными и продолжают независимо выполнять запись; это приводит к конфликтующим данным и повреждению. Предотвращается механизмами кворума и fencing. Один из самых опасных сценариев отказа в HA-кластерах.","tags":["split-brain","distributed","quorum","ha","consistency"]},{"term":"TDE","full":"Transparent Data Encryption","definition":"Прозрачный для приложения метод шифрования, при котором файлы базы данных (данные и журналы) хранятся на диске в зашифрованном виде. Защищает данные даже при краже дисков или файлов бэкапа (data at rest). Не защищает данные в сети (in transit); для этого нужен TLS.","tags":["tde","encryption","encryption","security","security"]},{"term":"RBAC","full":"Управление доступом на основе ролей","definition":"Модель управления доступом, при которой привилегии назначаются ролям, а не напрямую пользователям, а пользователи включаются в роли. Упрощает управление привилегиями и облегчает соблюдение принципа наименьших привилегий. Сохраняет масштабируемость администрирования при росте числа пользователей.","tags":["rbac","role","privilege","security","security"]},{"term":"Пул соединений","full":"","definition":"Пул, который заранее создаёт соединения и повторно использует их вместо постоянного открытия и закрытия соединений приложения с базой данных. Снижает затраты на установку соединения, ограничивает потребление ресурсов и контролирует нагрузку. Защищает базу данных, не допуская избыточного числа соединений.","tags":["connection-pool","connection-pool","performance","resource","application"]},{"term":"Подготовленный оператор","full":"","definition":"Конструкция, при которой SQL-оператор разбирается и планируется один раз, а затем многократно выполняется с разными значениями параметров. Повышает производительность за счёт повторного использования плана и защищает от SQL-инъекций. Используется вместе со связываемыми переменными.","tags":["prepared-statement","bind","security","sql-injection","performance"]},{"term":"Связываемая переменная","full":"","definition":"Параметр, передаваемый через заполнитель вместо встраивания значения в текст SQL. Эффективно использует кэш планов, не допуская повторного планирования одного и того же запроса для разных значений, и предотвращает SQL-инъекции. Критически важна для запросов с часто меняющимися литералами.","tags":["bind-variable","parameter","prepared-statement","plan-cache","security"]},{"term":"Parameter Sniffing","full":"","definition":"Оптимизатор строит и кэширует план подготовленного/параметризованного запроса на основе значения параметра при первом выполнении. Если это значение нетипично, может закрепиться план, плохо работающий для последующих других значений. Решения — перекомпиляция плана (recompile) и подсказки оптимизатору.","tags":["parameter-sniffing","plan-cache","optimizer","performance","sql-server"]},{"term":"VACUUM / Bloat","full":"","definition":"Из-за MVCC в таблице накапливаются мёртвые версии удалённых/обновлённых строк; это разрастание называется bloat. VACUUM освобождает место, занятое мёртвыми строками, и обновляет статистику (PostgreSQL). Если не запускать его регулярно, диск разрастается, а производительность падает.","tags":["vacuum","bloat","bloated","mvcc","postgresql"]},{"term":"Компакция","full":"","definition":"В хранилищах на основе LSM-дерева (например, Cassandra, RocksDB) процесс слияния множества мелких файлов данных и удаления мёртвых/устаревших записей. Повышает производительность чтения и освобождает место на диске. Аналог VACUUM в мире LSM.","tags":["compaction","lsm-tree","merge","cassandra","rocksdb"]},{"term":"Oplog","full":"Operations Log","definition":"В MongoDB — специальная коллекция фиксированного размера (capped), в которую по порядку записываются все операции записи, применённые на primary. Участники replica set читают её, чтобы оставаться синхронизированными с primary. Основа CDC и репликации.","tags":["oplog","mongodb","replication","cdc","log"]},{"term":"Binlog","full":"Binary Log","definition":"В MySQL — двоичный журнал, фиксирующий все операции (или изменения строк), модифицирующие данные. Используется для репликации, восстановления на момент времени и CDC. Может вестись в формате row, statement или mixed.","tags":["binlog","mysql","replication","cdc","recovery"]},{"term":"CDC","full":"Change Data Capture","definition":"Метод, который захватывает изменения insert/update/delete в базе данных и передаёт их потоком в другие системы (хранилище данных, поисковую систему, очередь сообщений). Обычно работает с низкими накладными расходами, читая журнал транзакций (binlog, WAL, oplog). Основа интеграции данных в реальном времени.","tags":["cdc","change-data-capture","data-streaming","binlog","integration"]},{"term":"Материализованное представление","full":"","definition":"Представление, физически хранящее результат запроса на диске; в отличие от обычного представления, оно не вычисляется заново при каждом запросе. Ускоряет дорогие агрегации и соединения, но его данные устаревают и требуют обновления через refresh. Распространено в OLAP/отчётности.","tags":["materialized-view","view","cache","olap","reporting"]},{"term":"Внешний ключ","full":"","definition":"Ограничение ссылочной целостности, позволяющее столбцу одной таблицы ссылаться на первичный/потенциальный ключ другой таблицы. Не допускает ссылок на несуществующие строки и сохраняет связи. Правила вроде ON DELETE/UPDATE CASCADE определяют поведение зависимых строк.","tags":["foreign-key","foreign-key","integrity","relationship","constraint"]},{"term":"Нормализация","full":"Нормализация","definition":"Процесс разделения данных на связанные таблицы для уменьшения избыточности и снижения риска несогласованности (нормальные формы 1NF, 2NF, 3NF и др.). Предотвращает аномалии обновления и предпочтительна в OLTP-системах. Взамен запросам может требоваться больше соединений.","tags":["normalization","normalization","design","oltp","redundancy"]},{"term":"Денормализация","full":"Денормализация","definition":"Метод намеренного дублирования данных для уменьшения числа соединений и повышения производительности чтения. Ускоряет запросы, но приводит к дублированию данных и усложняет обновления. Предпочтительна для отчётности/OLAP и нагрузок с преобладанием чтения.","tags":["denormalization","denormalization","performance","olap","read"]},{"term":"OLTP","full":"Online Transaction Processing","definition":"Тип системы, обрабатывающей большое число коротких, частых и параллельных транзакций (insert/update/delete, чтение отдельных строк); например, банковские системы, электронная коммерция. Требует низкой задержки и высокой параллельности, обычно проектируется нормализованной. Операционный аналог OLAP.","tags":["oltp","transaction","transaction","operational","normalization"]},{"term":"OLAP","full":"Online Analytical Processing","definition":"Тип системы, выполняющей сложные запросы агрегации и анализа над большими наборами данных; например, отчётность, бизнес-аналитика. Выполняется немного, но тяжёлых запросов; обычно проектируется денормализованной и колоночной. Аналитический аналог OLTP.","tags":["olap","analytics","reporting","data-warehouse","columnar"]},{"term":"ETL","full":"Extract, Transform, Load","definition":"Процесс извлечения данных из источников (Extract), их преобразования по бизнес-правилам (Transform) и загрузки в целевую систему, обычно хранилище данных (Load). Вариант, при котором преобразование выполняется в целевой системе после загрузки, называется ELT. Классический подход к интеграции данных.","tags":["etl","data-integration","data-warehouse","elt","transformation"]},{"term":"Горячие/холодные данные","full":"","definition":"Часто используемые актуальные данные называют «горячими», редко используемые старые/архивные — «холодными». Горячие данные хранят на быстрых/дорогих носителях, холодные — на дешёвых/медленных (data tiering). Стратегия баланса стоимости и производительности.","tags":["hot-data","cold-data","hot-cold","archive","tiering"]},{"term":"Фрагментация индекса","full":"","definition":"Со временем вставки/удаления/обновления приводят к тому, что логический порядок страниц индекса расходится с физическим, а заполненность страниц снижается. Это вызывает лишний ввод-вывод и потерю места, ухудшая производительность. Устраняется через REBUILD или REORGANIZE.","tags":["index-fragmentation","fragmentation","index","maintenance","performance"]},{"term":"Двухфазная фиксация","full":"2PC","definition":"Протокол, обеспечивающий атомарную фиксацию распределённой транзакции, охватывающей несколько баз данных/ресурсов. Работает в две фазы: сначала координатор спрашивает всех участников о готовности (prepare); если все согласны — выполняется commit, если хотя бы один отказывается — все выполняют откат. Медленный и чувствителен к отказу координатора.","tags":["two-phase-commit","2pc","distributed","distributed","atomic"]}]},"guide/glossary-2.json":{"terms":[{"term":"LSM-дерево","full":"Log-Structured Merge Tree","definition":"Структура хранения, которая сначала накапливает записи в структуре в памяти (memtable) и сбрасывает их на диск в виде отсортированных блоков. Превращает случайные записи в последовательные для высокой пропускной способности записи; чтение просматривает несколько уровней. Лежит в основе RocksDB, Cassandra и LevelDB.","tags":["storage","storage engine","lsm","write"]},{"term":"B+ дерево","full":"","definition":"Сбалансированная древовидная структура, в которой все данные хранятся в листовых узлах, а внутренние узлы содержат только ключи маршрутизации. Связанные листья ускоряют диапазонные сканирования. Самая распространённая структура индексов реляционных СУБД.","tags":["index","index structure","storage","storage engine","btree"]},{"term":"Фильтр Блума","full":"","definition":"Вероятностная структура данных, проверяющая при малом расходе памяти, входит ли элемент в множество. Отвечает «точно нет» или «вероятно, да»; ложноположительные ответы возможны, ложноотрицательные — нет. Используется в движках на основе LSM, чтобы избежать лишних чтений с диска.","tags":["storage","storage engine","probabilistic","filter","performance"]},{"term":"Согласованное хеширование","full":"","definition":"Метод распределения, при котором при добавлении или удалении узлов перемещается лишь небольшая доля ключей. Ключи и узлы размещаются на виртуальном кольце. Используется для распределения данных в распределённых кэшах и NoSQL-системах.","tags":["distributed systems","distributed","hashing","sharding","scaling"]},{"term":"Векторные часы","full":"","definition":"Структура, хранящая счётчик для каждого узла, чтобы отслеживать причинный порядок событий в распределённых системах. Определяет, происходит ли одно обновление от другого или они параллельны (конфликтуют). Используется для обнаружения конфликтов и в eventual consistency.","tags":["distributed systems","distributed","clock","causality","consistency"]},{"term":"Gossip-протокол","full":"","definition":"Метод обмена, при котором каждый узел периодически передаёт своё состояние нескольким случайно выбранным соседям, так что информация распространяется по всему кластеру подобно эпидемии. Не требует центрального координатора, масштабируем и отказоустойчив. Используется для управления членством в Cassandra и Consul.","tags":["distributed systems","distributed","gossip","cluster","membership"]},{"term":"Raft","full":"","definition":"Алгоритм консенсуса, созданный более простым для понимания, чем Paxos. Выбирает лидера, добавляет все записи в журнал, реплицируемый через лидера, и делает их надёжными после подтверждения большинством. Используется в etcd и Consul.","tags":["distributed systems","distributed","consensus","raft","replication"]},{"term":"Paxos","full":"","definition":"Классический алгоритм консенсуса, гарантирующий согласие большинства на одном значении в ненадёжной сети. Корректен, но считается сложным в реализации; составляет теоретическую основу многих распределённых систем. Используется в таких системах, как Google Chubby.","tags":["distributed systems","distributed","consensus","paxos","agreement"]},{"term":"Выбор лидера","full":"","definition":"Процесс выбора одного узла (лидера) для координации определённой задачи в распределённом кластере. При отказе текущего лидера новый выбирается автоматически. Обычно требует одобрения большинства (кворума) для предотвращения split-brain.","tags":["distributed systems","distributed","leader","election","coordination"]},{"term":"Кворумная запись/чтение","full":"","definition":"Минимальное число узлов, необходимое для того, чтобы чтение или запись считались действительными. При выполнении условия W + R > N гарантируется получение самых свежих данных. Позволяет настраивать компромисс между согласованностью и доступностью.","tags":["distributed systems","distributed","quorum","consistency","replication"]},{"term":"WAL Shipping","full":"Write-Ahead Log Shipping","definition":"Передача записей WAL, сформированных основным сервером, на резервные серверы и их воспроизведение там. Так резервный сервер поддерживает почти актуальную копию основной базы данных. Основа потоковой репликации PostgreSQL.","tags":["replication","replication","wal","high-availability","standby"]},{"term":"Log Shipping","full":"","definition":"Простой метод репликации, основанный на регулярном копировании бэкапов журнала транзакций на резервный сервер и их восстановлении. Отставание больше, чем при потоковой репликации, но настройка проста. Часто используется для аварийного восстановления в SQL Server.","tags":["replication","replication","log","backup","standby"]},{"term":"Snapshot Isolation","full":"","definition":"Уровень изоляции, при котором каждая транзакция видит согласованный снимок данных на момент своего начала. Читающие не блокируют пишущих; однако возможны некоторые аномалии, например write skew. Реализуется вместе с MVCC.","tags":["isolation level","isolation","mvcc","snapshot","concurrency"]},{"term":"Serializable Snapshot Isolation (SSI)","full":"Serializable Snapshot Isolation","definition":"Метод, обеспечивающий настоящую сериализуемость поверх snapshot isolation за счёт отслеживания опасных зависимостей чтения-записи. При обнаружении конфликта одна из транзакций откатывается. Используется на уровне SERIALIZABLE в PostgreSQL.","tags":["isolation level","isolation","serializable","ssi","concurrency"]},{"term":"Gap Lock","full":"","definition":"Блокировка промежутка между записями индекса (ещё не существующего диапазона), не позволяющая вставлять новые строки в этот диапазон. Используется для предотвращения фантомного чтения. Применяется на уровне REPEATABLE READ в InnoDB.","tags":["locking","lock","innodb","phantom","concurrency"]},{"term":"Next-Key Lock","full":"","definition":"Сочетание блокировки записи индекса и блокировки промежутка перед ней (gap lock). Предотвращает фантомные вставки, защищая и существующую строку, и диапазон. Стандартное поведение построчной блокировки в InnoDB.","tags":["locking","lock","innodb","gap","concurrency"]},{"term":"Оптимистическая блокировка","full":"","definition":"Метод, исходящий из того, что конфликты редки: работа идёт без блокировок, а при фиксации лишь проверяется, не изменились ли данные. Обычно реализуется с помощью номера версии или временной метки. При обнаружении конфликта операция повторяется.","tags":["locking","lock","optimistic","concurrency","concurrency"]},{"term":"Пессимистическая блокировка","full":"","definition":"Метод, исходящий из того, что конфликты будут: блокировка берётся сразу при чтении или изменении данных. Другие транзакции ждут её освобождения. Защищает целостность данных при высокой конкуренции, но снижает параллельность.","tags":["locking","lock","pessimistic","concurrency","concurrency"]},{"term":"Идемпотентность","full":"","definition":"Свойство, при котором однократное или многократное выполнение операции даёт один и тот же результат. Позволяет безопасно повторять операции после сетевых ошибок. В распределённых системах не допускает побочных эффектов от дублирующихся запросов.","tags":["distributed systems","distributed","idempotent","retry","reliability"]},{"term":"Circuit Breaker","full":"","definition":"Шаблон проектирования, защищающий систему путём временного прекращения вызовов сервиса, который постоянно даёт сбои. Пока цепь «разомкнута», запросы быстро отклоняются, а через некоторое время выполняется пробный вызов. Предотвращает каскадные отказы.","tags":["distributed systems","distributed","fault tolerance","resilience","pattern"]},{"term":"Шаблон Saga","full":"","definition":"Шаблон управления длительными транзакциями, охватывающими несколько сервисов: каждый шаг выполняется как локальная транзакция, а при сбое применяются компенсирующие транзакции. В распределённой среде обеспечивает eventual consistency вместо ACID. Распространён в микросервисных архитектурах.","tags":["distributed systems","distributed","saga","microservices","pattern"]},{"term":"Шаблон Outbox","full":"","definition":"Шаблон, при котором сообщение записывается в таблицу «outbox» в той же транзакции, чтобы обновление базы данных и публикация сообщения были атомарными. Отдельный процесс читает эту таблицу и публикует сообщение. Это исключает рассогласование между данными и публикацией событий.","tags":["distributed systems","distributed","outbox","messaging","pattern"]},{"term":"Fan-out","full":"","definition":"Распределение одной операции на множество подопераций или целей; например, запись поста в ленты тысяч подписчиков. Может выполняться при записи (write fan-out) или при чтении (read fan-out). Важное проектное решение при масштабировании.","tags":["distributed systems","distributed","scaling","fanout","architecture"]},{"term":"Горячая точка (hotspot)","full":"","definition":"Чрезмерная концентрация нагрузки на определённом узле, секции или ключе. Эта точка становится узким местом и ограничивает производительность всей системы. Частая причина — неудачно выбранный ключ шардирования.","tags":["distributed systems","distributed","hotspot","performance","sharding"]},{"term":"Перекос (дисбаланс данных)","full":"","definition":"Неравномерное распределение данных или нагрузки по секциям. Одни секции переполняются, другие остаются пустыми, и эффективность параллелизма падает. Балансируется выбором ключа или salting.","tags":["distributed systems","distributed","skew","imbalance","sharding"]},{"term":"Частичный индекс","full":"","definition":"Индекс, охватывающий только строки таблицы, удовлетворяющие условию WHERE. Уменьшает размер индекса, удешевляет обслуживание и ускоряет часто запрашиваемое подмножество. Используется, например, для индексации только активных строк.","tags":["index","index type","postgresql","partial","performance"]},{"term":"Индекс по выражению","full":"","definition":"Индекс, построенный по результату выражения или функции, а не по столбцу. Например, построенный по LOWER(email), он ускоряет поиск без учёта регистра. Запрос должен использовать то же выражение.","tags":["index","index type","expression","function","performance"]},{"term":"GIN-индекс","full":"Generalized Inverted Index","definition":"Тип инвертированного индекса, индексирующего несколько компонентов внутри одного значения. Идеален, когда поле содержит много ключей: массивы, JSONB, полнотекстовый поиск. Используется в PostgreSQL.","tags":["index","index type","postgresql","gin","jsonb"]},{"term":"GiST-индекс","full":"Generalized Search Tree","definition":"Расширяемая древовидная индексная инфраструктура; для разных типов данных можно определять собственные стратегии поиска. Хорошо подходит для геопространственных (geometry), диапазонных запросов и поиска ближайших соседей. Лежит в основе PostGIS.","tags":["index","index type","postgresql","gist","geospatial"]},{"term":"TOAST","full":"The Oversized-Attribute Storage Technique","definition":"Механизм PostgreSQL для сжатия больших значений полей, не помещающихся в страницу, и их хранения в отдельной области. Большие текстовые и JSONB-значения разбиваются прозрачно. Сохраняет производительность, поддерживая основную таблицу компактной.","tags":["storage","storage engine","postgresql","toast","large-values"]},{"term":"Index-Only Scan","full":"","definition":"Сканирование, при котором результат формируется только по индексу, без чтения таблицы, поскольку все нужные данные есть в индексе. Существенно сокращает обращения к диску. В PostgreSQL работает совместно с картой видимости (visibility map).","tags":["index","index scan","scan","performance","postgresql"]},{"term":"HTAP","full":"Hybrid Transactional/Analytical Processing","definition":"Подход, при котором транзакционная (OLTP) и аналитическая (OLAP) нагрузки выполняются совместно в одной системе. Устраняет задержку переноса данных в отдельное хранилище для анализа (ETL). Обычно сочетает строковое и колоночное хранение.","tags":["analytics","oltp","olap","htap","architecture"]},{"term":"Ключ шардирования","full":"","definition":"Столбец или столбцы, определяющие, в какой шард попадёт строка. Удачно выбранный ключ равномерно распределяет нагрузку и предотвращает горячие точки. Неудачный выбор ведёт к дисбалансу данных и дорогим межшардовым запросам.","tags":["distributed systems","distributed","sharding","key","scaling"]},{"term":"Ребалансировка","full":"","definition":"Перераспределение данных между секциями при добавлении или удалении узлов либо при разбалансировке нагрузки. Цель — выровнять нагрузку и эффективно использовать ресурсы. Во время выполнения создаёт сетевую нагрузку и нагрузку на ввод-вывод.","tags":["distributed systems","distributed","rebalancing","scaling","sharding"]},{"term":"Fencing","full":"","definition":"Механизм, предотвращающий повреждение данных путём запрета записи в общий ресурс для устаревшего или изолированного узла. Обычно используется возрастающий «fencing token», чтобы устаревшие запросы отклонялись. Ограничивает последствия split-brain.","tags":["distributed systems","distributed","fencing","safety","split-brain"]},{"term":"Watermark","full":"","definition":"В системах потоковой обработки — маркер прогресса, означающий, что все события до определённого момента времени считаются поступившими. Используется для обработки опоздавших событий и решения о закрытии оконных вычислений.","tags":["stream processing","streaming","watermark","event time","event"]},{"term":"Tombstone","full":"","definition":"Специальный маркер, записываемый для обозначения удаления записи. В LSM и распределённых системах, где данные не удаляются физически сразу, обеспечивает распространение удаления на все реплики. Фактическая очистка выполняется при компакции.","tags":["storage","storage engine","lsm","tombstone","delete"]},{"term":"Компакция (LSM)","full":"","definition":"Слияние и перезапись отсортированных файлов (SSTables) на диске в LSM-деревьях. Улучшает производительность чтения и использование диска за счёт удаления старых версий и tombstone. Выполняется в фоне и создаёт нагрузку на запись.","tags":["storage","storage engine","lsm","compaction","maintenance"]},{"term":"Read Repair","full":"","definition":"Метод, при котором обнаруженное при чтении расхождение между репликами устраняется записью самого свежего значения на отстающие узлы. Система приводит себя к согласованному состоянию за счёт обычного трафика чтения. Используется в таких системах, как Cassandra.","tags":["distributed systems","distributed","repair","consistency","replication"]},{"term":"Hinted Handoff","full":"","definition":"Сохранение записи в виде «подсказки» (hint) на другом узле, пока целевой узел временно недоступен, и её доставка после его возвращения. Гарантирует, что записи не теряются при кратковременных сбоях. Поддерживает eventual consistency.","tags":["distributed systems","distributed","handoff","availability","replication"]},{"term":"Согласованность в конечном счёте","full":"","definition":"Слабая модель согласованности, гарантирующая, что все реплики в итоге сойдутся к одному значению, если новые обновления прекратятся. Допускает кратковременные несоответствия, но обеспечивает высокую доступность и низкую задержку. Используется по умолчанию во многих NoSQL-системах.","tags":["distributed systems","distributed","consistency","eventual","nosql"]},{"term":"Строгая согласованность","full":"","definition":"Модель согласованности, при которой после завершения записи все читающие сразу видят самое свежее значение. Интуитивна в использовании, но из-за необходимости координации дорога по задержке и доступности. Предпочтительна в финансовых системах.","tags":["distributed systems","distributed","consistency","strong","strong consistency"]},{"term":"Линеаризуемость","full":"","definition":"Самый строгий уровень согласованности, гарантирующий, что каждая операция выглядит выполненной в один момент между своим началом и концом, а все операции укладываются в единый порядок реального времени. Система ведёт себя так, будто существует единственная копия. Дорога в реализации.","tags":["distributed systems","distributed","consistency","linearizability","strong consistency"]},{"term":"Двухфазная блокировка (2PL)","full":"Two-Phase Locking","definition":"Правило двухфазной блокировки: транзакция сначала только захватывает блокировки (фаза роста), а затем только освобождает их (фаза сокращения). Эта дисциплина гарантирует сериализуемость. В строгой форме (Strict 2PL) блокировки удерживаются до commit.","tags":["locking","lock","2pl","serializable","concurrency"]},{"term":"Обнаружение взаимоблокировок","full":"","definition":"Механизм обнаружения взаимоблокировки, когда две или более транзакции ждут друг друга из-за удерживаемых блокировок. Обычно выявляется поиском цикла в графе ожиданий (wait-for graph); одна транзакция выбирается жертвой и откатывается. После этого работа системы возобновляется.","tags":["locking","lock","deadlock","deadlock detection","concurrency"]}]},"guide/glossary-3.json":{"terms":[{"term":"Усиление записи","full":"Усиление записи","definition":"Ситуация, когда одна логическая запись вызывает гораздо больше физических записей на уровне хранения (компакция LSM, перезапись страниц, WAL, стирание блоков SSD). Высокие значения изнашивают диски и снижают пропускную способность записи.","tags":["storage","lsm","performance","ssd"]},{"term":"Усиление чтения","full":"Усиление чтения","definition":"Ситуация, когда одно логическое чтение фактически требует нескольких физических/внутренних чтений. Типичный пример — поиск ключа на многих уровнях LSM-дерева; фильтры Блума и компакция уменьшают его.","tags":["storage","lsm","performance"]},{"term":"LSM-tree","full":"Log-Structured Merge Tree","definition":"Структура данных, которая сначала принимает записи в структуру в памяти (memtable), сбрасывает их на диск в виде отсортированных блоков (SSTables) и сливает в фоне (compaction). Идеальна для нагрузок с интенсивной записью; используется в RocksDB и Cassandra.","tags":["storage","lsm","write-heavy"]},{"term":"B+tree","full":"B+ дерево","definition":"Классическая структура индексов реляционных СУБД. Все данные хранятся в листовых узлах, а листья связаны в список; обеспечивает сбалансированную производительность чтения для точечных и диапазонных запросов.","tags":["index","btree","storage"]},{"term":"Fill Factor","full":"Fill Factor","definition":"Коэффициент, задающий, насколько заполняется страница индекса/таблицы изначально. Оставленное свободное место уменьшает расщепления страниц при будущих обновлениях/вставках; fillfactor в PG, fill factor в MSSQL.","tags":["index","storage","tuning"]},{"term":"HOT Update","full":"Heap-Only Tuple Update","definition":"В PostgreSQL обновление, не изменяющее ни одного индексированного столбца, создаёт новую версию строки на той же странице и не обновляет индексы. Уменьшает разрастание индексов и снижает стоимость обновления.","tags":["postgres","mvcc","update","performance"]},{"term":"Bloat","full":"Table/Index Bloat","definition":"Ситуация в MVCC-движках, когда таблица/индекс занимают больше места, чем нужно, из-за неочищенных мёртвых версий строк или пустых страниц. Увеличивает стоимость сканирования; место освобождается через VACUUM/reorg.","tags":["mvcc","postgres","maintenance","bloat"]},{"term":"Autovacuum","full":"Автоматический VACUUM","definition":"Фоновый процесс PostgreSQL, автоматически выполняющий очистку мёртвых кортежей, сбор статистики и заморозку идентификаторов транзакций. Настраивается под нагрузку через пороговые параметры и параметры стоимости.","tags":["postgres","vacuum","maintenance"]},{"term":"Freeze / Wraparound","full":"Transaction ID Freeze / Wraparound","definition":"В PostgreSQL — пометка старых строк как «замороженных» (frozen), чтобы предотвратить переполнение (wraparound) 32-битных идентификаторов транзакций. Если этим пренебречь, база данных перестаёт принимать записи, чтобы не допустить потери данных.","tags":["postgres","mvcc","vacuum","wraparound"]},{"term":"Checkpoint","full":"Checkpoint","definition":"Событие, при котором грязные страницы из памяти записываются на диск и фиксируется точка, с которой начнётся восстановление. Сокращает время восстановления после сбоя; частота — компромисс между пиками ввода-вывода и временем восстановления.","tags":["recovery","wal","internals","checkpoint"]},{"term":"Fuzzy Checkpoint","full":"Fuzzy Checkpoint","definition":"Тип контрольной точки, при котором грязные страницы непрерывно записываются в фоне, распределённо во времени, без остановки системы. Сохраняет предсказуемость времени отклика, избегая резких всплесков ввода-вывода (checkpoint spikes).","tags":["recovery","checkpoint","internals"]},{"term":"Redo","full":"Redo Log / Roll Forward","definition":"Запись, сохраняемая для повторного применения (наката) изменений транзакции при восстановлении. Восстанавливает зафиксированные изменения, не попавшие на диск до сбоя.","tags":["recovery","oracle","wal","redo"]},{"term":"Undo","full":"Undo Log / Rollback","definition":"Запись, сохраняемая для отката транзакции и предоставления старых версий строк для MVCC. В Oracle/InnoDB старые версии читаются из сегментов undo.","tags":["mvcc","oracle","innodb","undo","rollback"]},{"term":"MVCC-снимок","full":"MVCC-снимок","definition":"Логическое представление на момент времени, определяющее, какие версии строк видимы, чтобы транзакция получала согласованное чтение. Создаётся в начале транзакции или оператора (в зависимости от уровня изоляции).","tags":["mvcc","concurrency","isolation","snapshot"]},{"term":"Аномалия изоляции","full":"Аномалии изоляции","definition":"Несогласованности, возникающие при параллельных транзакциях и недостаточной изоляции: dirty read (чтение незафиксированных данных), non-repeatable read (изменение той же строки), phantom (появление новых строк) и write skew (на первый взгляд независимые записи совместно нарушают правило).","tags":["isolation","concurrency","anomaly","acid"]},{"term":"2PL","full":"Two-Phase Locking","definition":"Протокол, обеспечивающий сериализуемость с помощью блокировок: транзакция сначала только захватывает блокировки (фаза роста), а после определённого момента только освобождает их (фаза сокращения). Strict 2PL удерживает блокировки до commit.","tags":["concurrency","locking","serializable","2pl"]},{"term":"MVTO","full":"Multi-Version Timestamp Ordering","definition":"Метод управления параллелизмом, который присваивает транзакциям временные метки и упорядочивает по ним чтения/записи многоверсионных данных. Разрешает конфликты временными метками вместо блокировок.","tags":["concurrency","mvcc","timestamp","protocol"]},{"term":"Mutex","full":"Mutual Exclusion","definition":"Примитив взаимного исключения, гарантирующий, что в каждый момент к ресурсу обращается только один поток. В СУБД играет роль, подобную latch, при защите структур общей памяти.","tags":["concurrency","internals","mutex"]},{"term":"Spinlock","full":"Spinlock","definition":"Очень кратковременная блокировка, при которой поток вместо засыпания крутится в цикле (busy-wait) до её освобождения. Дешева для очень коротких критических секций; при конкуренции расходует CPU.","tags":["concurrency","internals","spinlock","cpu"]},{"term":"Buffer Pool","full":"Buffer Pool / Buffer Cache","definition":"Кэш, уменьшающий дисковый ввод-вывод за счёт хранения часто используемых страниц данных в памяти. Алгоритм вытеснения (LRU, clock-sweep) решает, какая страница будет вытеснена; размер задаётся исходя из рабочего набора.","tags":["memory","cache","internals","buffer"]},{"term":"Рабочий набор","full":"Рабочий набор","definition":"Набор данных, к которому нагрузка активно обращается в заданном временном окне. Когда buffer pool достаточно велик, чтобы вместить этот набор, физический ввод-вывод минимален; отличается от общего объёма данных.","tags":["memory","cache","sizing","performance"]},{"term":"Перекос","full":"Перекос данных","definition":"Ситуация, когда значения распределены неравномерно и некоторые встречаются гораздо чаще других. Главная причина ошибок оценки кардинальности, проблем parameter sniffing и горячих точек при шардинге; решается с помощью гистограмм.","tags":["statistics","distribution","skew","performance"]},{"term":"Кэш планов","full":"Кэш планов","definition":"Область памяти, в которой скомпилированные планы выполнения хранятся для повторного использования. Избавляет от затрат на hard parse, но несёт риски parameter sniffing и устаревших планов; plan cache в MSSQL, library cache в Oracle.","tags":["optimizer","plan-cache","parsing"]},{"term":"Hard/Soft Parse","full":"Hard Parse / Soft Parse","definition":"Hard parse: SQL разбирается, оптимизируется и план строится с нуля (дорого). Soft parse: существующий план находится в кэше и используется повторно (дёшево). Связываемые переменные повышают долю soft parse.","tags":["parsing","oracle","performance","plan-cache"]},{"term":"Шторм подключений","full":"Шторм подключений (Connection Storm)","definition":"Одновременное открытие большого числа новых подключений к базе данных за короткое время. Перегружает базу затратами на установку соединений и нагрузкой на память; обычно вызвано исчерпанием пула или волной переподключений.","tags":["connections","pooling","overload"]},{"term":"Thundering Herd","full":"Проблема Thundering Herd","definition":"Одновременное обращение множества клиентов к одному ресурсу после какого-либо события (истечение кэша, освобождение блокировки, восстановление сервиса). Смягчается с помощью backpressure, jitter и паттерна single-flight.","tags":["concurrency","cache","overload","resilience"]},{"term":"Backpressure","full":"Backpressure","definition":"Замедление или отклонение производителя системой вместо приёма нагрузки, которую она не может обработать (лимиты очередей, rate limit). Обеспечивает плавную деградацию вместо отказа под перегрузкой.","tags":["resilience","flow-control","overload"]},{"term":"Lease (аренда)","full":"Lease (аренда)","definition":"Право на лидерство/ресурс, выдаваемое узлу на фиксированный срок и автоматически теряющее силу по его истечении. Благодаря ограничению по времени не позволяет зависшему узлу оставаться лидером бесконечно.","tags":["distributed","leader-election","lease"]},{"term":"Read-Your-Writes","full":"Согласованность Read-Your-Writes","definition":"Сессионная согласованность, гарантирующая, что пользователь всегда видит своё изменение при следующем же чтении. При маршрутизации на реплики с отставанием достигается специальной маршрутизацией/токенами.","tags":["distributed","consistency","session","replication"]},{"term":"Причинная согласованность","full":"Причинная согласованность","definition":"Модель согласованности сильнее eventual, гарантирующая, что причинно связанные операции видны на всех узлах в одном и том же порядке. Несвязанные (конкурентные) операции могут быть видны в разном порядке.","tags":["distributed","consistency","causal"]},{"term":"CAP","full":"Consistency, Availability, Partition tolerance (согласованность, доступность, устойчивость к разделению)","definition":"Теорема, согласно которой при сетевом разделении распределённая система может выбрать только одно: согласованность или доступность. Без разделения этот компромисс не обязателен.","tags":["distributed","cap","consistency","theory"]},{"term":"PACELC","full":"Теорема PACELC","definition":"Модель, расширяющая CAP: при разделении (P) выбор между доступностью (A) и согласованностью (C), иначе (E) — между задержкой (L) и согласованностью (C). Учитывает также компромисс задержка/согласованность при нормальной работе.","tags":["distributed","cap","pacelc","latency","theory"]},{"term":"Горячая секция","full":"Горячая секция / горячая точка","definition":"Непропорциональное скопление нагрузки на одной секции/шарде. Причина — ключ шардирования с низкой кардинальностью, основанный на времени или монотонно возрастающий; этот узел становится узким местом, и масштабирование перестаёт работать.","tags":["distributed","sharding","hot-spot","skew"]},{"term":"Columnstore","full":"Колоночное хранение","definition":"Формат, в котором данные хранятся по столбцам, а не по строкам. Аналитические запросы читают только нужные столбцы, с высоким сжатием и векторизованным выполнением; идеально для OLAP.","tags":["olap","columnar","analytics","compression"]},{"term":"Векторизованное выполнение","full":"Векторизованное выполнение","definition":"Операторы запроса обрабатывают блок (пакет) значений столбца за раз, а не построчно. Значительно повышает пропускную способность аналитических запросов за счёт лучшего использования кэшей CPU и SIMD.","tags":["olap","execution","performance","columnar"]},{"term":"Zone Map / Min-Max","full":"Zone Map / индекс Min-Max","definition":"Лёгкие метаданные, хранящие минимальное и максимальное значение столбца для каждого блока данных. Снижают стоимость сканирования, пропуская блоки, которые не могут удовлетворять предикату запроса, без их чтения (block skipping).","tags":["olap","index","pruning","columnar"]},{"term":"Логическая репликация","full":"Логическая репликация","definition":"Репликация, публикующая изменения на уровне строк (логические события) и подписывающаяся на них, вместо физических блоков. Позволяет выбирать отдельные таблицы, реплицировать между версиями и в обе стороны; в PG реализуется через publication/subscription.","tags":["replication","postgres","logical","cdc"]},{"term":"GTID","full":"Глобальный идентификатор транзакции","definition":"Глобально уникальный идентификатор, присваиваемый каждой транзакции в репликации MySQL. Отслеживание позиции реплики по ID транзакции вместо файла/смещения делает failover и переконфигурацию безопасными и простыми.","tags":["mysql","replication","gtid","failover"]},{"term":"High Water Mark","full":"High Water Mark","definition":"Наибольшая граница блоков, когда-либо использованная в сегменте/таблице. В Oracle полное сканирование таблицы читает до этой границы; поскольку она не опускается после массовых удалений, это может приводить к сканированию пустых блоков и раздуванию.","tags":["oracle","storage","internals","scan"]}]},"playbooks/playbooks.json":{"schemaVersion":1,"playbooks":[{"id":"db-slow","title":"База данных тормозит","icon":"🐢","symptom":"Замедление всего приложения; запросы выполняются намного дольше обычного, пользователи жалуются.","steps":[{"title":"Сначала уточните, что именно медленно","detail":"Медленно всё или конкретный запрос/экран? Когда началось и совпадает ли с деплоем/патчем/ростом данных? Проблема на всей системе или в одной БД? Это сужает поиск до одной оси: CPU, I/O, блокировки или смена плана."},{"title":"Посмотрите активные сессии и wait events","detail":"Что выполняется и что ожидает прямо сейчас? Доминирующий wait event (I/O, блокировка, журнал) указывает на узкое место.","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":"Проверьте блокировки","detail":"Самая частая причина замедления — одна сессия блокирует другие. Найдите блокирующую сессию и определите голову цепочки.","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":"Найдите самые дорогие запросы","detail":"Найдите 10 запросов, потребляющих больше всего ресурсов. Обычно 90% проблемы дают несколько запросов.","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":"Изучите план подозрительного запроса","detail":"Получите EXPLAIN для медленного запроса. Ищите полные сканирования таблиц, отсутствующие индексы, неудачный порядок соединений или устаревшую статистику. Если план внезапно изменился, рассмотрите сбор статистики.","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":"Проверьте системные ресурсы","detail":"На уровне ОС проверьте CPU, ожидание I/O (iowait), память и использование диска. Проверьте cache hit ratio; если он низкий, возможна нехватка памяти. Проблема может быть вне БД (заполненный диск, шумная соседняя VM)."},{"title":"Решите: быстрое или постоянное исправление","detail":"Для немедленного облегчения: завершите вышедший из-под контроля запрос, добавьте отсутствующий индекс, соберите статистику. Для постоянного решения: переработайте запрос/индекс, исправьте настройки памяти, обеспечьте стабильность плана (baseline/hint)."}],"related":["active-sessions","blocking-sessions","top-slow-queries","explain-plan","cache-hit-ratio"]},{"id":"disk-full","title":"Диск / табличное пространство заполнено","icon":"💽","symptom":"Ошибки 'unable to extend', 'disk full', 'log file full'; запись останавливается.","steps":[{"title":"Без паники: сначала найдите, что заполнено","detail":"Это файл данных, журнал транзакций, область archive/redo, temp или диск ОС? Очистка не того места не поможет. Проверьте диск через df -h на уровне ОС и представления пространства внутри БД."},{"title":"Измерьте использование","detail":"Какое табличное пространство/файл/каталог заполнено и на сколько процентов?","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":"Найдите крупнейших потребителей","detail":"Определите самые большие таблицы/сегменты, самые большие файлы журналов/архивов и ненужные старые бэкапы. Чаще всего виновата одна разросшаяся таблица или накопившиеся архивные журналы.","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":"Безопасно освободите место (быстро)","detail":"Для журналов/архивов: сделайте бэкап, затем удалите старые архивные журналы/бэкапы (Oracle: RMAN delete archivelog; MSSQL: сделайте бэкап журнала и сожмите журнал). В Redis: maxmemory-policy и вытеснение (eviction). НИКОГДА не удаляйте действующий файл данных вручную; НИКОГДА не удаляйте архивные журналы без бэкапа."},{"title":"Увеличьте ёмкость постоянно","detail":"Добавьте место в файл данных или включите autoextend (Oracle: ALTER DATABASE DATAFILE ... AUTOEXTEND ON MAXSIZE), увеличьте диск, добавьте табличное пространство. Устраните первопричину: ротация журналов, задание очистки архивов, политика хранения."},{"title":"Предотвратите повторение","detail":"Настройте оповещения об использовании диска (пороги 80%/90%), автоматизируйте очистку архивов/журналов и чётко определите политику хранения. Не допускайте, чтобы этот плейбук понадобился снова."}],"related":["free-space","largest-tables","database-size","reclaim-space","last-backup-status"]},{"id":"blocking","title":"Кризис блокировок","icon":"🔒","symptom":"Запросы зависают, 'lock wait timeout', много сессий ожидают, но загрузка CPU низкая.","steps":[{"title":"Найдите вершину цепочки блокировок (root blocker)","detail":"Выясните, кто кого блокирует; настоящий виновник — сессия, которая сама ничего не ждёт, но заставляет ждать других. Сосредоточьтесь на сессии на вершине цепочки.","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":"Выясните, что делает корневая блокирующая сессия (root blocker)","detail":"Это открытая, но не зафиксированная транзакция? Приложение оставило её в состоянии 'idle in transaction'? Это длинный пакетный процесс? Разберитесь, прежде чем завершать вслепую — возможно, она вот-вот закончится.","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":"Решите: ждать, решить через приложение или завершить","detail":"Если она вот-вот завершится — подождите. Если приложение может выполнить commit/rollback — инициируйте это. Иначе, если работа бизнеса стоит, завершите корневую блокирующую сессию. Перед завершением оцените последствия (время отката, данные)."},{"title":"При необходимости завершите корневую блокирующую сессию (root blocker)","detail":"Завершайте только сессию в голове цепочки; сессии под ней освобождаются автоматически.","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":"Устраните первопричину","detail":"Отсутствующий индекс может вызывать широкие блокировки; приложение может слишком долго держать транзакции открытыми; уровень изоляции может быть неподходящим. Добавьте индексы, сократите транзакции, задайте тайм-аут 'idle in transaction' и выберите подходящий уровень изоляции."}],"related":["blocking-sessions","kill-session","open-transactions"]},{"id":"connection-storm","title":"Шторм подключений (слишком много соединений)","icon":"🌊","symptom":"'too many connections', 'ORA-00018', 'remaining connection slots reserved' — новые подключения отклоняются.","steps":[{"title":"Оцените текущее состояние","detail":"Сколько подключений, каков лимит и сколько из них активны, а сколько простаивают?","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":"Определите источник: кто/откуда","detail":"Какое приложение/хост/пользователь открывает подключения? Обычно переполнен пул одного сервера приложений или есть утечка подключений (масса сессий idle in transaction).","engines":{"postgres":"SELECT client_addr, usename, state, count(*) FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY 1,2,3 ORDER BY 4 DESC;","mysql":"SELECT host, user, count(*) FROM information_schema.processlist GROUP BY host, user ORDER BY 3 DESC;","mssql":"SELECT host_name, program_name, login_name, COUNT(*) FROM sys.dm_exec_sessions WHERE is_user_process=1 GROUP BY host_name, program_name, login_name ORDER BY 4 DESC;"}},{"title":"Немедленно получите передышку","detail":"Очистите долго простаивающие подключения и подключения idle-in-transaction. При необходимости массово сбросьте подключения проблемного приложения. Используйте слот, зарезервированный для входа администратора (PG superuser_reserved_connections)."},{"title":"Правильное решение: пул подключений","detail":"Неограниченное увеличение max_connections расходует память и лишь откладывает проблему. Правильное решение — пул подключений на стороне приложения (HikariCP, PgBouncer, ProxySQL) с разумным размером пула. Размер пула обычно около (ядра*2 + число дисков) — см. калькулятор Tools > Connection Pool."},{"title":"Предотвратите утечки и повторение","detail":"Убедитесь, что приложение закрывает подключения (finally/using). Задайте idle_in_transaction_session_timeout и wait_timeout. Настройте оповещение по числу подключений."}],"related":["connection-count","kill-session","open-transactions"]},{"id":"replication-lag","title":"Отставание / остановка репликации","icon":"🔁","symptom":"Standby/реплика отстаёт или полностью остановилась; реплики для чтения показывают устаревшие данные.","steps":[{"title":"Каково отставание и растёт ли оно или стабильно","detail":"Каково отставание в секундах/байтах? Это постоянный сдвиг или он непрерывно растёт? Если растёт — реплика не успевает применять изменения; если стабилен — была кратковременная остановка.","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":"Работает ли репликация, есть ли ошибки","detail":"Каково состояние потоков IO/SQL, процесса применения, не разорвано ли соединение? Если есть ошибка (например, конфликтующая строка, отсутствующая таблица), применение остановлено; прочитайте сообщение об ошибке.","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":"Найдите узкое место","detail":"Почему реплика не успевает? Однопоточное применение плюс высокая нагрузка на запись (PG/MySQL в основном применяют последовательно), узкое место I/O на standby, сеть или длинный запрос, блокирующий применение (PG hot_standby_feedback / recovery conflict)?"},{"title":"Ликвидируйте отставание","detail":"Ускорьте применение: включите параллельное применение (MySQL replica_parallel_workers — slave_parallel_workers до 8.0.26; в PG его нет, но есть параметры восстановления), улучшите I/O на standby, устраните запрос, блокирующий применение. Если остановилось с ошибкой: исправьте ошибку (вручную разрешите конфликтующую строку) и возобновите репликацию."},{"title":"Пересоздайте, если она сломана","detail":"Если реплика сильно отстала/сломана, чище всего пересинхронизировать: пересоздайте standby из свежего бэкапа (PG pg_basebackup, MySQL clone/xtrabackup, Oracle RMAN duplicate). Критично: найдите причину до пересоздания, иначе это повторится."}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"high-cpu","title":"Высокая загрузка CPU","icon":"🔥","symptom":"CPU сервера загружен на 90%+; вся система работает медленно, load average высокий.","steps":[{"title":"Убедитесь, что CPU действительно потребляет БД","detail":"С помощью top/mpstat на уровне ОС убедитесь, что CPU потребляет именно процесс БД. Иногда потребитель — соседний процесс или бэкап/антивирус. Если iowait высокий, проблема в диске, а не в CPU."},{"title":"Найдите запросы, больше всего нагружающие CPU","detail":"Выведите топ запросов, отсортированных по времени CPU. Обычно 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":"Проверьте проблему плана / индекса","detail":"Получите план проблемного запроса. CPU сжигают полные сканирования таблиц, взрыв nested loop, сброс hash join на диск или плохие планы из-за parameter sniffing/устаревшей статистики. Добавьте индекс или соберите статистику."},{"title":"Немедленно снимите нагрузку","detail":"Завершите вышедший из-под контроля запрос и при необходимости ограничьте его через Resource Manager/Resource Governor. Если нагрузка возникла внезапно, проверьте последний деплой/изменение параметров."},{"title":"Исправьте окончательно","detail":"Исправьте запрос/индекс, обеспечьте стабильность плана и запланируйте задание сбора статистики. Если CPU постоянно на 100%, может потребоваться масштабирование (добавление CPU, распределение чтения на реплики для чтения)."}],"related":["top-slow-queries","explain-plan","active-sessions","update-statistics"]},{"id":"memory-pressure","title":"Давление на память / OOM","icon":"🧠","symptom":"OOM killer завершает процесс, 'out of memory', низкий cache hit ratio, чрезмерный свопинг, Redis 'OOM command not allowed'.","steps":[{"title":"Кто потребляет память","detail":"Это выделенная память БД (buffer pool/SGA), память сессий (PGA/sort/work_mem) или ОС в целом? Проверьте free/vmstat на уровне ОС и представления памяти внутри БД.","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":"Отделите конфигурацию от нагрузки","detail":"Превышает ли сумма настроек памяти физическую RAM (over-commit)? Посчитайте shared_buffers + (work_mem * параллелизм). Или это кратковременная нагрузка (огромная сортировка/хеш, много подключений)?"},{"title":"Специфика Redis: вытеснение и фрагментация","detail":"Если used_memory достиг maxmemory, проверьте maxmemory-policy (при noeviction запись отклоняется). Если mem_fragmentation_ratio высокий, используйте MEMORY PURGE / activedefrag. Найдите большие ключи через --bigkeys.","engines":{"redis":"CONFIG GET maxmemory-policy\nMEMORY DOCTOR\nredis-cli --bigkeys"}},{"title":"Немедленно снимите нагрузку","detail":"Завершите вышедший из-под контроля запрос/сессию, поглощающую память, временно уменьшите память сессий, например work_mem, сбросьте ненужные подключения. Если система уходит в своп, приоритет — выйти из свопа."},{"title":"Правильно рассчитайте размеры","detail":"Правильно распределите RAM с помощью калькулятора Tools > Memory/Buffer Pool. Общее правило: на выделенном сервере buffer pool/SGA ~70-80% RAM; общая память сессий, умноженная на параллелизм, не должна превышать RAM. При необходимости добавьте RAM."}],"related":["cache-hit-ratio","show-config-param","reclaim-space"]},{"id":"log-growth","title":"Раздувание журнала транзакций / undo","icon":"📈","symptom":"Файл журнала транзакций или область undo/redo быстро растёт; 'log full', 'ORA-01555 snapshot too old', 'unable to extend undo'.","steps":[{"title":"Найдите открытую транзакцию, раздувающую журнал","detail":"Журнал/undo нельзя очистить, пока не завершится самая старая активная транзакция. Одна длинная открытая транзакция может удерживать весь журнал.","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":"Поймите первопричину, специфичную для СУБД","detail":"MSSQL: в модели восстановления FULL журнал растёт бесконечно, если не делаются бэкапы журнала. Oracle: ORA-01555 = недостаточные undo retention/размер + длинный запрос. PG: если replication slot не продвигается, WAL накапливается; длинная транзакция блокирует VACUUM."},{"title":"Правильное действие для журнала MSSQL","detail":"Сделайте бэкап журнала (BACKUP LOG) — это делает журнал повторно используемым. Если модель восстановления действительно должна быть SIMPLE, измените её. Затем при необходимости (и только при необходимости) сожмите журнал.","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","detail":"Непродвигающийся (неактивный) replication slot вызывает накопление WAL. Перед удалением проверьте владельца слота (slot_type, database, plugin): это может быть временно отключившийся standby или логический подписчик/CDC (например, Debezium). При удалении слота подключённый standby придётся пересоздать, а сторону подписчика/CDC — пересинхронизировать. Удаляйте его, только если он действительно не используется; для долговременной защиты задайте верхнюю границу через 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":"Настройте undo/retention и предотвратите повторение","detail":"Oracle: увеличьте табличное пространство UNDO, задайте undo_retention с учётом длинных запросов (или RETENTION GUARANTEE). Не допускайте длинных открытых транзакций в приложении. Контролируйте выполнение задания бэкапа журнала."}],"related":["open-transactions","reclaim-space","last-backup-status"]},{"id":"deadlocks","title":"Анализ взаимоблокировок","icon":"♻️","symptom":"'deadlock detected', 'ORA-00060', 'chosen as deadlock victim' — транзакции блокируют друг друга, и одна из них прерывается.","steps":[{"title":"Взаимоблокировки нормальны, но не должны быть частыми","detail":"БД обнаруживает взаимоблокировку и разрешает её, выбирая жертву — так задумано. Цель — снизить частоту. Приложение должно повторять транзакцию-жертву. Сначала получите граф взаимоблокировки."},{"title":"Получите граф/детали взаимоблокировки","detail":"Какие две (или более) сессии, какие ресурсы и какие режимы блокировки конфликтовали?","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":"Устраните паттерн конфликта","detail":"Самая частая причина: две транзакции обращаются к ресурсам в ПРОТИВОПОЛОЖНОМ порядке. Обращайтесь к ресурсам в ОДНОМ И ТОМ ЖЕ порядке во всех ветках кода. Вторая по частоте причина: широкие блокировки диапазонов из-за отсутствующего индекса — добавьте нужный индекс."},{"title":"Сократите длительность и область блокировок","detail":"Держите транзакции короткими (не включайте взаимодействие с пользователем в транзакцию). Выберите подходящий уровень изоляции. При необходимости захватывайте блокировки строк заранее (SELECT ... FOR UPDATE с детерминированным порядком)."},{"title":"Повтор в приложении обязателен","detail":"Полностью исключить взаимоблокировки невозможно; реализуйте автоматический повтор (exponential backoff) для транзакции-жертвы. Так пользователь не увидит ошибку."}],"related":["blocking-sessions","open-transactions","list-indexes"]},{"id":"db-down","title":"База данных не открывается / недоступна","icon":"🚨","symptom":"Не удаётся подключиться к сервису, 'connection refused', экземпляр не запускается, listener не отвечает.","steps":[{"title":"Где обрыв: сеть, сервис или экземпляр","detail":"Доступен ли сервер (ping/ssh)? Запущен ли процесс/сервис БД (ps/systemctl/services.msc)? Слушается ли порт (ss -ltnp / telnet host port)? Это локализует проблему на одном уровне: сеть / сервис / экземпляр базы данных."},{"title":"Проверьте сервис и listener","detail":"Если процесс БД не запущен, попробуйте его запустить; если запущен, но подключения не проходят, проверьте listener/порт и уровень аутентификации.","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":"Читайте журналы — ответ там","detail":"Если БД не открывается, причина записана в журнале: alert log (Oracle), postgresql.log / startup, mysqld error log, SQL Server ERRORLOG, журнал redis. Заполненный диск, повреждённый файл, права доступа, конфликт портов, отсутствующий файл — всё видно здесь."},{"title":"Разберите типичные причины: диск, права, порт, повреждение","detail":"Диск заполнен на 100% (самая частая причина)? Нарушены права на каталог данных? Порт занят другим процессом? Требуется восстановление (после сбоя)? Повреждён control file/redo log? Действуйте по каждому пункту исходя из сообщения в журнале."},{"title":"Восстановите работу и зафиксируйте инцидент","detail":"Восстановление после сбоя (crash recovery) обычно завершается автоматически; наберитесь терпения. При повреждении восстанавливайтесь из бэкапа/PITR (см. плейбук «Повреждение данных»). После этого напишите анализ первопричины (RCA): что произошло, почему и как предотвратить повторение."}],"related":["version-uptime","connect-cli","last-backup-status"]},{"id":"corruption","title":"Повреждение данных","icon":"🧯","symptom":"'ORA-01578', 'page corruption', 'checksum mismatch', 'MISCONF', ошибки целостности I/O при чтении.","steps":[{"title":"Сохраняйте спокойствие и остановите распространение","detail":"Паническое вмешательство усугубляет ситуацию. Сначала определите масштаб и, если возможно, остановите запись в затронутый объект. Если проблема аппаратная (диск/контроллер), проверьте также журналы ОС/оборудования — исправление БД не исправит диск."},{"title":"Определите масштаб: сколько повреждено","detail":"Это один блок/страница/объект или повреждение обширное? Запустите проверку целостности.","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":"Приоритет: восстановление из бэкапа","detail":"При повреждении самый безопасный путь — восстановление из исправного бэкапа. Если поддерживается восстановление на уровне блока/страницы, восстановите только повреждённую часть (Oracle RMAN BLOCKRECOVER, MSSQL page restore). Инструменты исправления (DBCC REPAIR_ALLOW_DATA_LOSS, innodb_force_recovery) ПРИВОДЯТ К ПОТЕРЕ ДАННЫХ — это крайняя мера."},{"title":"Проверьте целостность","detail":"После восстановления повторите проверку (DBCC CHECKDB / RMAN VALIDATE / CHECK TABLE) и убедитесь, что ошибок нет. Также проверьте согласованность данных на стороне приложения (число строк, критичные итоги)."},{"title":"Первопричина: обычно оборудование","detail":"Повреждение обычно вызвано сбоем диска/памяти/контроллера. Протестируйте/замените оборудование, включите контрольные суммы (data_checksums, PAGE_VERIFY CHECKSUM) и ужесточите процедуру бэкапа и проверки. Запланируйте регулярные DBCC CHECKDB / RMAN VALIDATE."}],"related":["last-backup-status","restore-dump","db-down"]},{"id":"long-running-query","title":"Долгий запрос / задание","icon":"⏱️","symptom":"Один запрос/задание выполняется часами и не завершается; потребляет ресурсы или мешает другим.","steps":[{"title":"Зафиксируйте запрос и его длительность","detail":"Какой запрос, сколько он уже выполняется, какой пользователь/приложение?","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":"Выполняется или завис","detail":"Он действительно выполняется или ждёт блокировку? Смотрите индикаторы прогресса (Oracle v$session_longops, представления PG pg_stat_progress_*). Если ждёт — переходите к сценарию Blocking.","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":"Решите: ждать или отменять","detail":"Если запрос продвигается и скоро завершится, подождите (стоимость отката при отмене может сделать его ещё дольше). Если он завис, работает неправильно или мешает production, завершите его. Перед отменой учтите время отката."},{"title":"При необходимости завершите","detail":"Предпочтительно отменить только запрос (сохранив сессию); если это не помогает, завершите сессию.","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":"Исправьте окончательно","detail":"Оптимизируйте запрос (индекс, план, постраничная/пакетная обработка), перенесите тяжёлые задания на непиковые часы и при необходимости выполняйте их частями. Для повторяющихся отчётов рассмотрите materialized view / реплику для чтения."}],"related":["active-sessions","kill-session","explain-plan","top-slow-queries"]},{"id":"xid-wraparound","title":"Postgres XID wraparound / кризис autovacuum","icon":"🧬","symptom":"'database is not accepting commands to avoid wraparound data loss'; autovacuum не успевает, datfrozenxid стареет.","steps":[{"title":"Оцените срочность","detail":"Идентификаторы транзакций PostgreSQL 32-битные; по мере их исчерпания сервер ОСТАНАВЛИВАЕТ ЗАПИСЬ, чтобы предотвратить потерю данных. Это критическая ситуация, ограниченная по времени. Хорошая новость: она решаема; нужна дисциплина, а не паника."},{"title":"Измерьте, насколько близок предел","detail":"Получите возраст самого старого незамороженного XID для каждой базы данных; близость к autovacuum_freeze_max_age (по умолчанию 200M) показывает степень опасности.","engines":{"postgres":"SELECT datname, age(datfrozenxid) AS xid_age\nFROM pg_database ORDER BY xid_age DESC;"}},{"title":"Найдите самые старые таблицы","detail":"Проблема обычно в нескольких больших/часто изменяемых таблицах; сосредоточьтесь на самых старых значениях relfrozenxid.","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":"Устраните то, что блокирует VACUUM","detail":"Длительные открытые транзакции, заброшенные слоты репликации и prepared transactions блокируют очистку (freeze). Найдите и устраните их — иначе VACUUM не продвинется, сколько бы он ни работал.","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":"Выполните агрессивный FREEZE","detail":"После устранения блокирующих факторов принудительно выполните заморозку, начиная с самых старых таблиц. Даже если сервер перестал выдавать новые идентификаторы транзакций, не переходите в однопользовательский режим (postgres --single): в актуальной документации PostgreSQL сказано, что он больше не нужен и его следует избегать. Достаточно подключиться суперпользователем в обычном режиме и выполнить VACUUM; в PG14+ при приближении wraparound срабатывает механизм failsafe у VACUUM.","engines":{"postgres":"VACUUM (FREEZE, VERBOSE) oldest_table;\n-- Entire database: vacuumdb --all --freeze --jobs=4"}},{"title":"Навсегда: усильте и отслеживайте autovacuum","detail":"Увеличьте autovacuum_vacuum_cost_limit, уменьшите autovacuum_naptime и задайте параметры хранения на уровне таблиц для интенсивно изменяемых таблиц. Настройте оповещение по age(datfrozenxid); больше никогда не попадайте в этот кризис."}],"related":["open-transactions","reclaim-space"]},{"id":"tempdb-contention","title":"Конкуренция в tempdb (SQL Server)","icon":"🌀","symptom":"Ожидания PAGELATCH_UP/EX, конкуренция за страницы распределения tempdb (PFS/GAM/SGAM), общее замедление.","steps":[{"title":"Подтвердите конкуренцию","detail":"Ожидания PAGELATCH на системных страницах tempdb (например, 2:1:1) указывают на конкуренцию за распределение.","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":"Разберите использование tempdb","detail":"Кто и сколько использует tempdb? Внутренние объекты (сбросы sort/hash), version store или пользовательские объекты?","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":"Исправьте конфигурацию файлов","detail":"Используйте для tempdb несколько файлов данных ОДИНАКОВОГО размера, по числу ядер CPU (до 8). SQL 2016+ рекомендует это при установке; в старых версиях используйте флаги трассировки 1117/1118. Разместите файлы на быстром диске (локальный NVMe)."},{"title":"Найдите «разбушевавшийся» запрос","detail":"Запросы с большими сбросами sort/hash раздувают tempdb. Ищите запросы с недостаточным memory grant или плохими планами; уменьшайте сбросы с помощью индексов/планов.","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":"Аналоги в других базах данных","detail":"Та же «конкуренция за временное пространство» в Oracle — это табличное пространство TEMP (v$tempseg_usage / v$sort_usage), а в PostgreSQL — temp_files и переполнение work_mem. Коренное решение всегда одно: исправить запрос со сбросами и ускорить временное пространство."}],"related":["free-space","top-slow-queries","reclaim-space"]},{"id":"cert-expiry","title":"TLS / истёк сертификат","icon":"🔐","symptom":"'certificate has expired', 'SSL handshake failed', клиенты внезапно не могут подключиться (без изменений в коде).","steps":[{"title":"Где сбой: сервер, клиент или CA?","detail":"Истёк сертификат сервера, клиентский сертификат или CA в цепочке? Типичный симптом: «вчера работало, сегодня нет, код не менялся»."},{"title":"Проверьте срок действия","detail":"Прочитайте дату истечения сертификата прямо с сервера (быстрый способ, не зависящий от СУБД).","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":"Установите и загрузите новый сертификат","detail":"Замените сертификат и в большинстве случаев перезагрузите конфигурацию сервиса без перезапуска.","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":"Проверьте","detail":"Убедитесь командой openssl выше, что TLS handshake с новым сертификатом проходит и срок действия продлён; выполните сквозное подключение клиентом."},{"title":"Навсегда: больше никогда","detail":"Настройте оповещение за 30 дней до истечения сертификата; используйте автоматическое продление (ACME/Let's Encrypt, HashiCorp Vault, cert-manager). Автоматизируйте перезагрузку после продления."}],"related":["connect-cli"]},{"id":"upgrade-rollback","title":"Откат обновления версии / патча","icon":"⏪","symptom":"Проблемы после обновления/патча: регрессия производительности, несовместимость приложения или база данных не открывается.","steps":[{"title":"Сначала остановитесь: есть ли план отката?","detail":"Если вы НЕ сделали полный бэкап + (по возможности) снимок хранилища ДО обновления production, самый безопасный путь — исправлять вперёд. В большинстве СУБД откат означает восстановление из бэкапа. Этот шаг следовало спланировать ДО обновления."},{"title":"Классифицируйте проблему","detail":"Это регрессия плана оптимизатора (запросы замедлились), несовместимость приложения/драйвера или база не открывается / повреждение? От этого сильно зависит путь решения."},{"title":"Путь отката по СУБД","detail":"Oracle: откат патча через datapatch или Flashback Database (если вы создали guaranteed restore point). PostgreSQL: откат после pg_upgrade = старые бинарные файлы + бэкап старого каталога данных. MySQL: DOWNGRADE на месте в целом НЕ ПОДДЕРЖИВАЕТСЯ — восстанавливайте из бэкапа. SQL Server: понижения версии нет — восстанавливайте из бэкапа, сделанного до обновления. В любом случае снимок/бэкап — ваша страховка."},{"title":"Если это регрессия плана: исправьте без отката","detail":"Обычно откатывать всю версию не нужно; изменились только планы.","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":"Навсегда: в следующий раз","detail":"Сначала протестируйте обновление на staging с реальной нагрузкой, применяйте canary/rolling-обновления, создавайте guaranteed restore point / снимок и документируйте процедуру отката."}],"related":["last-backup-status","restore-dump","version-uptime"]},{"id":"es-red-cluster","title":"Кластер Elasticsearch red/yellow и неназначенные шарды","icon":"🟥","symptom":"_cluster/health 'red' или 'yellow'; неназначенные шарды; чтение/запись ограничены или данные недоступны.","steps":[{"title":"Цвет и причина","detail":"yellow = реплики шардов не назначены (данные есть, отказоустойчивость снижена). red = отсутствует хотя бы один PRIMARY-шард (эти данные недоступны). Сначала определите, какой случай и почему.","engines":{"elasticsearch":"GET _cluster/health?level=indices\nGET _cluster/allocation/explain"}},{"title":"Выведите неназначенные шарды и причину","detail":"Столбец unassigned.reason указывает первопричину (NODE_LEFT, ALLOCATION_FAILED, DISK и т. д.).","engines":{"elasticsearch":"GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state"}},{"title":"Устраните типичные причины","detail":"1) Disk watermark → выше low узел не получает новые шарды; на flood_stage индексы переходят в режим только для чтения. 2) Узел упал / вышел из кластера. 3) Allocation оставлен отключённым. 4) Число реплик > числа подходящих узлов."},{"title":"Исправление","detail":"Снимите блокировку в зависимости от причины.","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":"Если всё ещё red и primary действительно потерян","detail":"Восстановите затронутый индекс из snapshot или переиндексируйте его из исходных данных (_reindex). Навсегда: оповещения по disk watermark, подходящее число реплик (≥1) и мониторинг состояния узлов."}],"related":["replication-status","list-replicas","free-space"]},{"id":"time-skew","title":"Рассинхронизация часов / проблема NTP","icon":"⏱️","symptom":"Расхождение часов между узлами кластера; несогласованность репликации/выборов, 'certificate not yet valid', скачки времени в журналах.","steps":[{"title":"Распознайте симптом","detail":"Расхождение часов коварно: «мигание» выборов в Mongo/Redis Sentinel, ошибки TLS 'not yet valid/expired', ошибки Kerberos, метки времени, идущие назад в журналах. В распределённых системах это корень большинства «необъяснимых» странностей."},{"title":"Измерьте расхождение","detail":"На каждом узле сравните источник времени и смещение: `timedatectl`, `chronyc tracking` (или `ntpq -p`) и `date -u` на разных узлах. На сколько ms/s они расходятся?"},{"title":"Исправьте NTP/chrony","detail":"Запущен ли chronyd/ntpd, доступны ли источники времени, есть ли синхронизация? Запустите службу и исправьте источник; при больших смещениях корректируйте время осторожно (резкий скачок может затронуть некоторые СУБД)."},{"title":"Проверьте затронутый компонент","detail":"После исправления часов проверьте: стабильны ли выборы в наборе реплик MongoDB, исправен ли Redis Sentinel/Cluster, проходят ли TLS handshake, восстановились ли метрики отставания репликации?"},{"title":"Навсегда","detail":"Сделайте NTP обязательным на всех узлах и отслеживайте смещение (оповещение: >100ms). На виртуальных машинах определитесь с настройкой синхронизации времени host-guest; желательно использовать единую надёжную иерархию источников времени."}],"related":["replication-status"]}]},"playbooks/hadr.json":{"schemaVersion":1,"playbooks":[{"id":"hadr-oracle-dataguard","title":"Настройка Oracle Data Guard (physical standby)","icon":"🛡️","symptom":"Цель: настроить аварийное восстановление и высокую доступность для Oracle с physical standby (Data Guard); иметь возможность переключиться на standby при сбое primary.","steps":[{"title":"Переведите primary в режимы FORCE LOGGING и ARCHIVELOG","detail":"Чтобы standby оставался согласованным с primary, все изменения должны записываться в redo. FORCE LOGGING журналирует даже операции NOLOGGING. Для режима ARCHIVELOG база данных поднимается в состоянии MOUNT.","engines":{"oracle":"ALTER DATABASE FORCE LOGGING;\n-- For ARCHIVELOG:\nSHUTDOWN IMMEDIATE;\nSTARTUP MOUNT;\nALTER DATABASE ARCHIVELOG;\nALTER DATABASE OPEN;"}},{"title":"Добавьте standby redo log","detail":"В standby redo logs (SRL) должно быть на одну группу больше, чем в online redo logs, и тот же размер. Они создаются и на primary, и на standby; они необходимы для real-time apply. В примере online redo 200M, значит и SRL по 200M.","engines":{"oracle":"ALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl01.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl02.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl03.log') SIZE 200M;\nALTER DATABASE ADD STANDBY LOGFILE ('/u01/oradata/srl04.log') SIZE 200M;"}},{"title":"Задайте log_archive_dest и связанные параметры","detail":"На primary определите dest_2 для передачи redo на standby. Задайте DB_UNIQUE_NAME, LOG_ARCHIVE_CONFIG (db_unique_name обеих сторон), FAL_SERVER и STANDBY_FILE_MANAGEMENT=AUTO. Так файлы будут автоматически создаваться на 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":"Создайте standby с помощью RMAN DUPLICATE FOR STANDBY","detail":"После настройки файла паролей и статического listener/tnsnames на сервере standby RMAN подключается к primary и к standby (AUXILIARY). FROM ACTIVE DATABASE копирует по сети без бэкапа; DORECOVER применяет 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":"Запустите применение redo (MRP) и проверьте","detail":"Запустите managed recovery, пока standby находится в состоянии MOUNT; для real-time apply используйте USING CURRENT LOGFILE. Проверьте через v$managed_standby, что процесс применения (MRP0) работает.","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":"Включите Data Guard Broker","detail":"Broker централизует конфигурацию и сводит failover/switchover к одной команде. Задайте DG_BROKER_START=TRUE на обоих экземплярах, затем создайте и включите конфигурацию в dgmgrl. В 12c+ broker управляет транспортом redo: если LOG_ARCHIVE_DEST_2 был задан вручную, ADD DATABASE завершается ошибкой ORA-16698; сначала очистите его командой 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":"Отслеживайте разрыв синхронизации и завершите тестирование","detail":"Разницу sequence между primary и standby нужно отслеживать постоянно; растущий разрыв указывает на узкое место в сети/применении. dgmgrl SHOW CONFIGURATION должен показывать 'SUCCESS'. При желании протестируйте смену ролей через 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","icon":"🔁","symptom":"Цель: настроить автоматическую отработку отказа и читаемую secondary-реплику для SQL Server с Always On Availability Group (AG).","steps":[{"title":"Настройте Windows Server Failover Cluster (WSFC)","detail":"Always On опирается на WSFC. Установите компонент Failover Clustering на всех узлах и настройте сеть и witness. Создайте кластер с помощью 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":"Включите Always On на каждом экземпляре","detail":"Включите компонент Always On Availability Groups через SQL Server Configuration Manager или PowerShell. Затем перезапустите службу SQL Server. Это связывает членство в кластере с экземпляром.","engines":{"mssql":"-- PowerShell:\nEnable-SqlAlwaysOn -ServerInstance node1\\SQL01 -Force\nEnable-SqlAlwaysOn -ServerInstance node2\\SQL01 -Force"}},{"title":"Сделайте бэкап базы данных и восстановите его на secondary WITH NORECOVERY","detail":"База данных, добавляемая в AG, должна быть в модели восстановления FULL. Сделайте полный бэкап и бэкап журнала на primary; восстановите их на secondary WITH NORECOVERY (чтобы она могла продолжать получать redo). Это ручной seeding; альтернатива — 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":"Создайте endpoints и определите Availability Group","detail":"Создайте HADR endpoint на порту 5022 для трафика redo между узлами. Синхронный режим (SYNCHRONOUS_COMMIT) обеспечивает автоматическую отработку отказа без потери данных; асинхронный (ASYNCHRONOUS_COMMIT) работает с отставанием, но производителен для удалённого 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":"Присоедините secondary к AG и добавьте базу данных","detail":"Выполните JOIN к AG на экземпляре secondary, затем добавьте восстановленную базу данных в AG. Этот шаг нужен при ручном seeding; при automatic seeding это выполняется на primary через SET(SEEDING_MODE).","engines":{"mssql":"-- On the secondary:\nALTER AVAILABILITY GROUP appag JOIN;\nALTER DATABASE app SET HADR AVAILABILITY GROUP = appag;"}},{"title":"Создайте Availability Group Listener","detail":"Listener — виртуальное имя и IP, к которым подключаются приложения; после отработки отказа подключения автоматически перенаправляются на новый primary. Используйте это имя и MultiSubnetFailover=True в строке подключения приложения.","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":"Протестируйте отработку отказа и проверьте состояние синхронизации","detail":"Когда синхронизация в состоянии 'HEALTHY', протестируйте смену ролей плановой отработкой отказа. Для автоматической отработки отказа режим должен быть синхронным и FAILOVER_MODE=AUTOMATIC. Отслеживайте состояние через 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: потоковая репликация + автоматическая отработка отказа","icon":"🐘","symptom":"Цель: настроить hot standby для PostgreSQL с потоковой репликацией и добавить Patroni/repmgr для автоматической отработки отказа.","steps":[{"title":"Задайте параметры WAL и подключений на primary","detail":"Для потоковой репликации wal_level должен быть не ниже 'replica'. max_wal_senders должен покрывать число реплик + подключений для бэкапа, а преждевременное удаление WAL нужно предотвратить с помощью wal_keep_size или слота репликации. Изменения требуют перезапуска.","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":"Определите роль репликации и доступ в pg_hba","detail":"Создайте роль с атрибутом REPLICATION для подключения standby. Добавьте в pg_hba.conf строку, разрешающую доступ к базе 'replication' с IP standby, и перезагрузите конфигурацию через 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":"Создайте постоянный слот репликации на primary","detail":"Физический слот репликации не даёт primary удалять нужные WAL, даже когда standby отстаёт; управление через слоты безопаснее, чем wal_keep_size. Имя слота будет использовано в конфигурации standby.","engines":{"postgres":"SELECT pg_create_physical_replication_slot('standby1_slot');\nSELECT slot_name, active, restart_lsn FROM pg_replication_slots;"}},{"title":"Сделайте базовый бэкап на сервере standby с помощью pg_basebackup","detail":"При пустом каталоге данных standby снимите полную копию с primary. Флаг -R автоматически записывает файл standby.signal и primary_conninfo; -C может также создать слот. Имя слота передаётся через -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":"Проверьте primary_conninfo и hot_standby, запустите standby","detail":"Проверьте primary_conninfo (host, user, slot) в postgresql.auto.conf, записанном флагом -R; наличие файла standby.signal определяет режим standby. При hot_standby=on standby принимает запросы только на чтение. Запустите службу.","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":"Отслеживайте отставание репликации с primary","detail":"На primary pg_stat_replication показывает отправленный/применённый LSN и отставание для каждого standby. write/flush/replay lag должны оставаться небольшими; sync_state равен 'sync', если используется синхронная реплика.","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":"Добавьте Patroni или repmgr для автоматической отработки отказа","detail":"Голая потоковая репликация требует ручного promote. Для автоматической отработки отказа нужен менеджер кластера: Patroni (выбор лидера на etcd/Consul + автоматический promote) или repmgr (мониторинг/отработка отказа с repmgrd). На стороне приложения предоставьте единую точку подключения через HAProxy/pgBouncer. Если нужен ручной promote: pg_ctl promote. Перед promote убедитесь, что старый primary действительно остановлен и приложения не могут в него писать (риск split-brain). Для возврата старого primary в качестве standby нового primary нужен pg_rewind; для этого должны быть включены wal_log_hints = on или контрольные суммы данных.","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)","icon":"🐬","symptom":"Цель: настроить высокую доступность с автоматической отработкой отказа для MySQL с помощью InnoDB Cluster (Group Replication + MySQL Shell + MySQL Router).","steps":[{"title":"Выполните предварительные требования","detail":"InnoDB Cluster требует MySQL 8.0+, уникальный server_id, режим GTID и движок хранения InnoDB на каждом узле. Все экземпляры должны быть доступны друг другу по сети, а у таблиц должны быть первичные ключи. Подготовьте пользователя clusterAdmin для администрирования.","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":"Настройте каждый экземпляр с помощью MySQL Shell","detail":"В mysqlsh dba.configureInstance() подготавливает каждый узел для Group Replication; исправляет недостающие параметры и при необходимости запрашивает перезапуск. Повторите на всех узлах.","engines":{"mysql":"-- mysqlsh:\ndba.configureInstance('icadmin@node1:3306')\ndba.configureInstance('icadmin@node2:3306')\ndba.configureInstance('icadmin@node3:3306')"}},{"title":"Создайте кластер (seed-узел)","detail":"Подключитесь к одному узлу и создайте кластер через dba.createCluster(); этот узел становится первым primary. Возвращается объект cluster, который используется для последующих добавлений.","engines":{"mysql":"-- mysqlsh, while connected to node1:\nvar cluster = dba.createCluster('appCluster')"}},{"title":"Добавьте остальные экземпляры в кластер","detail":"Добавьте secondary-узлы через cluster.addInstance(). В качестве recoveryMethod выберите clone (физическое клонирование, быстро для нового узла) или incremental (через GTID). Минимум 3 узла обеспечивают нечётное большинство и автоматическую отработку отказа.","engines":{"mysql":"-- mysqlsh:\ncluster.addInstance('icadmin@node2:3306', {recoveryMethod: 'clone'})\ncluster.addInstance('icadmin@node3:3306', {recoveryMethod: 'clone'})"}},{"title":"Выполните bootstrap MySQL Router","detail":"MySQL Router выполняет маршрутизацию между приложением и кластером; предоставляет порты чтения/записи (6446) и только чтения (6447) и автоматически перенаправляет трафик при смене primary. Bootstrap генерирует конфигурацию из метаданных кластера.","engines":{"mysql":"mysqlrouter --bootstrap icadmin@node1:3306 --user=mysqlrouter --directory /etc/mysqlrouter\n# Then: mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf &"}},{"title":"Проверьте состояние кластера и отработку отказа","detail":"cluster.status() показывает роль каждого узла (R/W primary, R/O secondary) и состояние (ONLINE). clusterName и топология должны быть в порядке. Протестируйте отработку отказа: остановите primary и наблюдайте автоматический выбор нового 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 + шардинг","icon":"🍃","symptom":"Цель: настроить набор реплик с автоматической отработкой отказа для MongoDB и расширить его до шардированного кластера для горизонтального масштабирования.","steps":[{"title":"Запустите процессы mongod с одинаковым именем replSet","detail":"Запустите каждый член набора реплик с одинаковым именем --replSet и доступным --bind_ip. В production рекомендуется внутренняя аутентификация через keyFile или TLS. Стремитесь к нечётному числу голосующих членов для чёткого большинства.","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":"Инициализируйте набор реплик","detail":"Подключитесь к одному члену через mongosh, вызовите rs.initiate() и определите всех членов. Это запускает первые выборы, и выбирается primary. Проверьте роли PRIMARY/SECONDARY через 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":"Добавьте участников, задайте приоритеты и при необходимости арбитр","detail":"Добавляйте членов позже через rs.add(). priority определяет, какой член с большей вероятностью станет primary (0 = никогда не станет primary). Arbiter только голосует и не хранит данных; используется для разрешения ничьей при чётном числе узлов с данными, но для сохранности данных предпочтителен полноценный член. В топологиях с arbiter (особенно PSA) majority write concern не может быть выполнен при отказе узла с данными, и запись ожидает; в 5.0+ перед добавлением arbiter может потребоваться явно задать кластерный write concern по умолчанию через setDefaultRWConcern.","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":"Настройте набор реплик config server для шардинга","detail":"Метаданные шардированного кластера хранятся в наборе реплик config server (--configsvr). Он сам инициализируется как набор реплик; для высокой доступности рекомендуются 3 члена.","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":"Запустите наборы реплик шардов и маршрутизатор mongos","detail":"Каждый шард — отдельный набор реплик, запущенный с --shardsvr (сохранность данных обеспечивается репликацией внутри шарда). Маршрутизатор mongos запускается с указанием config servers; приложения подключаются к 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":"Добавьте шарды и шардируйте коллекцию","detail":"Подключитесь к mongos и добавьте каждый набор реплик шарда через sh.addShard(). Включите шардинг для базы данных и распределите коллекцию по ключу шардирования через sh.shardCollection(). Выбор ключа критичен: он должен иметь высокую кардинальность и равномерное распределение. Отслеживайте распределение чанков через 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":"Высокая доступность с Redis Sentinel + Cluster","icon":"⚡","symptom":"Цель: настроить развёртывание master-replica с автоматической отработкой отказа для Redis с помощью Sentinel; при необходимости горизонтального масштабирования перейти в режим Redis Cluster.","steps":[{"title":"Настройте master и реплики","detail":"Запустите один master и минимум две реплики. Реплики следуют за master через REPLICAOF ('replicaof <host> <port>' можно также указать в redis.conf). Для персистентности включите AOF или RDB; рекомендуется аутентификация через requirepass/masterauth.","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":"Настройте Sentinel: monitor и quorum","detail":"Определите отслеживаемый master и quorum в sentinel.conf каждого узла Sentinel. quorum — минимальное число Sentinel, необходимое, чтобы считать master «down». В production рекомендуются 3 Sentinel на 3 отдельных серверах (против 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":"Запустите процессы Sentinel","detail":"Запустите Sentinel на каждом узле. Sentinel автоматически обнаруживают друг друга и реплики; они записывают конфигурацию в файл во время работы. Поэтому sentinel.conf должен быть доступен для записи.","engines":{"redis":"redis-sentinel /etc/redis/sentinel.conf\n# or:\nredis-server /etc/redis/sentinel.conf --sentinel"}},{"title":"Запросите состояние через Sentinel и протестируйте отработку отказа","detail":"Запросите текущий master и реплики командами SENTINEL. Приложения должны узнавать текущий master, запрашивая у Sentinel 'get-master-addr-by-name'. Проверьте автоматическое переключение плановой отработкой отказа.","engines":{"redis":"SENTINEL master mymaster\nSENTINEL get-master-addr-by-name mymaster\nSENTINEL replicas mymaster\n# planned switchover:\nSENTINEL failover mymaster"}},{"title":"Альтернатива: разделение данных с Redis Cluster","detail":"Если памяти/пропускной способности одного master недостаточно, используйте Redis Cluster: данные разбиваются на 16384 hash slots и распределяются по нескольким master; у каждого master своя реплика. Приложение должно использовать клиент с поддержкой кластера. Настройка — через 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":"Проверьте состояние кластера","detail":"После создания кластера убедитесь, что все слоты назначены и узлы связаны. cluster_state должен быть 'ok', а все 16384 слота — назначены. Отслеживайте узлы и распределение слотов командами CLUSTER.","engines":{"redis":"redis-cli --cluster check 10.0.0.11:6379\nredis-cli -c -h 10.0.0.11 CLUSTER INFO\nredis-cli -c -h 10.0.0.11 CLUSTER NODES"}}],"related":["replication-status","list-replicas","promote-failover"]},{"id":"hadr-elasticsearch-ha","title":"Отказоустойчивость Elasticsearch: реплики + snapshot + CCR","icon":"🔎","symptom":"Цель: обеспечить отказоустойчивость Elasticsearch с помощью реплик шардов, выделенных master/data-узлов, резервных копий через снапшоты (SLM) и межкластерной репликации (CCR).","steps":[{"title":"Разделение ролей: выделенные master- и data-узлы","detail":"Для стабильности кластера голосующие master-eligible узлы (желательно 3, нечётное большинство) отделяются от рабочей нагрузки данных. Назначайте роли через node.roles в elasticsearch.yml и задайте seed hosts для обнаружения. 3 master-узла предотвращают 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":"Задайте число реплик индекса","detail":"number_of_replicas — число копий каждого primary-шарда; оно должно быть не меньше 1, чтобы данные оставались доступными при потере data-узла. Его можно менять динамически на работающем индексе; число реплик должно быть меньше числа data-узлов.","engines":{"elasticsearch":"PUT /orders/_settings\n{\n  \"index\": { \"number_of_replicas\": 1 }\n}\n# default via template (composable template; the old _template API is legacy):\n# PUT _index_template/order-tpl { \"index_patterns\": [\"order*\"], \"template\": { \"settings\": { \"number_of_replicas\": 1 } } }"}},{"title":"Определите репозиторий снапшотов","detail":"Резервные копии сохраняются в репозиторий: общую файловую систему (fs) или через плагины S3/GCS/Azure. Чтобы восстановить данные даже после удаления кластера, репозиторий должен находиться на надёжном хранилище вне кластера.","engines":{"elasticsearch":"PUT /_snapshot/backup_repo\n{\n  \"type\": \"fs\",\n  \"settings\": { \"location\": \"/mnt/es_backups\", \"compress\": true }\n}"}},{"title":"Настройте автоматические снапшоты с SLM","detail":"Snapshot Lifecycle Management (SLM) делает регулярные снапшоты и применяет политику хранения. Выражение cron задаёт периодичность, включаемые индексы и число хранимых снапшотов.","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":"Проверьте снапшот и восстановление","detail":"Запустите политику SLM вручную, чтобы убедиться в её работе; состояние снапшота должно быть SUCCESS. Регулярно проводите тесты восстановления: непроверенный бэкап — не бэкап.","engines":{"elasticsearch":"POST /_slm/policy/daily_backup/_execute\nGET /_snapshot/backup_repo/_all\n# restore:\nPOST /_snapshot/backup_repo/<snapshot_name>/_restore"}},{"title":"Настройте удалённый DR с Cross-Cluster Replication (CCR)","detail":"Для аварийного восстановления между регионами второй кластер (follower) следует за индексами удалённого основного кластера (leader). Сначала определите удалённый кластер, затем привяжите follower-индекс к leader; изменения на leader поступают автоматически. CCR — функция уровня Platinum (коммерческая).","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":"Усиление безопасности: SQL-базы данных (Oracle, PostgreSQL, MySQL, SQL Server)","icon":"🔒","symptom":"Цель: усилить защиту реляционных баз данных в стиле CIS — учётные записи по умолчанию, строгая парольная политика, минимальные привилегии, сетевые ограничения, TLS, аудит и шифрование (TDE).","steps":[{"title":"Удалите или заблокируйте стандартные/ненужные учётные записи, удалите демонстрационные схемы","detail":"Учётные записи по умолчанию и демонстрационные схемы, поставляемые с установкой, — хорошо известная поверхность атаки. Заблокируйте или удалите неиспользуемые; переименуйте учётные записи верхнего уровня/служебные.","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":"Введите строгую парольную политику и срок действия паролей","detail":"Слабые или никогда не меняющиеся пароли — самая распространённая точка входа. Ограничьте число попыток входа, задайте сложность и срок действия паролей; храните пароли со стойким хешем.","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":"Минимальные привилегии: отзовите PUBLIC/широкие привилегии, выдавайте привилегии через роли","detail":"Широкие привилегии, выданные всем (PUBLIC), облегчают горизонтальное перемещение. Отзовите их, создайте роли и выдавайте только необходимое.","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":"Ограничьте сеть/доступ — подключаться могут только известные клиенты","detail":"Не допускайте, чтобы база данных была открыта для всего мира. Ограничьте адрес прослушивания, примените правила доступа по хосту/IP и ограничьте порт межсетевым экраном.","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":"Требуйте TLS/зашифрованные подключения","detail":"Сетевой трафик уязвим для пассивного перехвата. Настройте сертификат сервера и требуйте от клиентов зашифрованных подключений.","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":"Включите аудит — сделайте видимым, кто что сделал","detail":"Журнал аудита необходим для контроля привилегированного доступа и подозрительной активности. Регистрируйте входы, изменения привилегий и доступ к чувствительным объектам.","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":"Примените шифрование данных (at rest / TDE)","detail":"При краже диска, бэкапа или файла данные должны оставаться нечитаемыми. Включите шифрование хранимых данных (TDE) на уровне столбца/табличного пространства/базы данных.","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":"Своевременно устанавливайте патчи и отключайте ненужные функции","detail":"Известные уязвимости — наиболее эксплуатируемый риск. Регулярно устанавливайте патчи безопасности; отключайте неиспользуемые протоколы, плагины и функции (сокращайте поверхность атаки).","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":"Усиление безопасности: MongoDB / Redis / Elasticsearch","icon":"🛡️","symptom":"Цель: усилить защиту NoSQL-хранилищ данных в стиле CIS — аутентификация, сетевые ограничения, блокировка опасных команд, TLS, роли с минимальными привилегиями и аудит.","steps":[{"title":"ВКЛЮЧИТЕ аутентификацию — закройте доступ без аутентификации","detail":"Эти продукты часто поставляются с ОТКЛЮЧЁННОЙ по умолчанию аутентификацией и оказываются скомпрометированы, если открыты в интернет. Включите аутентификацию и создайте пользователя-администратора.","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":"Ограничьте сеть — открывайте только необходимые интерфейсы и порты","detail":"Не допускайте, чтобы служба слушала весь мир на 0.0.0.0. Привяжите адрес к внутреннему интерфейсу/loopback и включите 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":"Ограничьте опасные команды","detail":"Одна команда может стереть все данные. Переименуйте или удалите разрушительные/административные команды либо запретите адресацию по шаблону (wildcard).","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":"Включите TLS/зашифрованные подключения","detail":"Учётные данные и данные не должны передаваться по открытой сети в незашифрованном виде. Включите TLS для клиентского и межузлового трафика.","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":"Выдавайте роли с минимальными привилегиями — избегайте широких ролей","detail":"Давайте каждому пользователю только тот доступ, который нужен для его задач. Вместо широких/административных ролей используйте детализированные роли, ограниченные конкретными базами данных, шаблонами ключей или индексами.","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":"Включите аудит/журналирование — контролируйте доступ и изменения","detail":"Попытки несанкционированного доступа и административные операции должны регистрироваться. Включите журнал аудита и отправляйте его в централизованное хранилище.","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":"Управляемые облачные базы данных (RDS/Aurora/Cloud SQL/Atlas/Azure SQL)","icon":"☁️","symptom":"Цель: изучить ключевые отличия управляемых облачных баз данных от самостоятельно управляемых установок; понять, как в облаке работают резервное копирование, параметры, отработка отказа, мониторинг, обновления и подключение.","steps":[{"title":"Смена мышления: вы работаете с сервисом, а не с сервером","detail":"В управляемых базах данных у вас НЕТ shell-доступа к ОС, файловой системе или процессу базы данных; нельзя зайти по SSH, вручную править конфигурационные файлы или стать суперпользователем. Доступны только API/консоль управления провайдера и обычное SQL-подключение. AWS RDS/Aurora: управление на уровне ресурса (экземпляр/кластер), сервер скрыт. GCP Cloud SQL: только API экземпляра, ОС недоступна. MongoDB Atlas: полностью управляемый кластер, доступа к mongod нет. Azure SQL Database: PaaS, ни сервер, ни ОС под ним не видны (Managed Instance даёт несколько больше контроля). Поэтому большинство привычек самостоятельного администрирования вроде «посмотреть файл», «убить процесс» или «запустить утилиту ОС» не применимы."},{"title":"Бэкап: автоматические снапшоты + восстановление на момент времени (PITR)","detail":"Вы не планируете бэкапы — провайдер делает их автоматически. Восстановление обычно означает «восстановить на определённый момент времени» (PITR) или создать новый экземпляр из снапшота; существующий экземпляр не перезаписывается. AWS RDS/Aurora: автоматические бэкапы + окно хранения; восстановление PITR с точностью до секунды открывается как новый экземпляр/кластер; возможны и ручные снапшоты. GCP Cloud SQL: автоматические бэкапы + PITR через бинарные журналы; восстановление выполняется в новый экземпляр. MongoDB Atlas: непрерывный облачный бэкап + PITR (M10+); восстановление из снапшотов. Azure SQL: встроенные автоматические бэкапы, хранение 1-35 дней, PITR на любой момент и восстановление удалённых БД. Общее правило: проверяйте конфигурацию бэкапов (окно, срок хранения) и регулярно проводите тесты восстановления."},{"title":"Параметры: группы параметров / флаги, а не конфигурационный файл","detail":"Вместо ручного редактирования postgresql.conf или my.cnf используется абстракция параметров провайдера; часть параметров заблокирована, а часть требует перезапуска (статические). AWS RDS/Aurora: задаются через DB parameter group (и cluster parameter group); динамические параметры применяются сразу, статические — после перезагрузки. GCP Cloud SQL: «database flags» задаются из консоли/gcloud. MongoDB Atlas: ограниченные «Additional Settings» + размер oplog и т. п.; большинство внутренних параметров управляется провайдером. Azure SQL: большинство настроек уровня сервера управляется провайдером; поведение меняется через SCOPED CONFIGURATION и выбор уровня сервиса. Перед применением проверьте, динамический параметр или статический."},{"title":"Высокая доступность / отработка отказа: управляемые Multi-AZ и реплики","detail":"Отработку отказа вы не строите сами — достаточно поставить галочку и доверить это провайдеру. Standby и автоматическая отработка отказа управляются провайдером, а подключения перенаправляются на новый primary через стабильный endpoint. AWS RDS: Multi-AZ (синхронный standby, автоматическая отработка отказа); Aurora: хранилище с несколькими копиями + автоматическая отработка отказа, cluster endpoint указывает на writer. GCP Cloud SQL: конфигурация HA (региональная, standby во вторичной зоне) одним щелчком; реплики для чтения добавляются отдельно. MongoDB Atlas: набор реплик встроен, доступно развёртывание в нескольких регионах/облаках, отработка отказа автоматическая. Azure SQL: встроенная HA (локальные реплики в Business Critical), вариант с избыточностью по зонам и межрегиональная active geo-replication / failover groups. Приложение всегда должно подключаться к endpoint, а не к фиксированному IP."},{"title":"Мониторинг: сервис метрик и журналов провайдера","detail":"Поскольку запускать утилиты ОС (top, iostat) нельзя, мониторинг ведётся через платформу метрик провайдера; там находятся метрики CPU, числа подключений, IOPS, отставания репликации и хранилища. AWS: метрики CloudWatch + Enhanced Monitoring + Performance Insights (анализ на уровне запросов). GCP Cloud SQL: Cloud Monitoring (ранее Stackdriver) + Query Insights. MongoDB Atlas: встроенные метрики/графики Atlas + Performance Advisor + панель реального времени. Azure SQL: Azure Monitor + SQL Analytics + Query Performance Insight. Всегда настраивайте пороговые оповещения (использование хранилища, CPU, лимит подключений, отставание реплик)."},{"title":"Обновление версий и обслуживание: из консоли, в окне обслуживания","detail":"Вместо ручного обновления бинарных файлов вы выбираете целевую версию; патчи минорных версий в основном применяются автоматически, а обновления мажорных версий планируются и обычно связаны с простоем/отработкой отказа HA. AWS RDS/Aurora: выбор версии движка + опция автоматического минорного обновления + малорисковые мажорные обновления через Blue/Green Deployments. GCP Cloud SQL: окно обслуживания + самостоятельное обновление мажорной версии. MongoDB Atlas: обновление версии одним щелчком, почти без простоя, поэтапно (rolling). Azure SQL: патчи применяются платформой автоматически (понятие версии в значительной мере абстрагировано); поведение управляется уровнем совместимости (compatibility level). Всегда сначала проверяйте на тестовой/staging-копии и планируйте окно обслуживания вне рабочего времени."},{"title":"Подключение и безопасность: endpoint + IAM/TLS","detail":"Подключения всегда идут через DNS endpoint, предоставляемый провайдером, и шифруются (TLS); сетевой доступ и идентификацию можно привязать к облачной системе управления идентификацией. AWS RDS/Aurora: endpoint экземпляра/кластера + Security Group для сети + опциональная IAM-аутентификация в базе данных + TLS (сертификат rds-ca). GCP Cloud SQL: публичный IP + Authorized Networks или частный IP/VPC, Cloud SQL Auth Proxy, IAM-аутентификация в базе данных, TLS. MongoDB Atlas: строка подключения SRV (mongodb+srv://), IP access list + private endpoint/peering, SCRAM или IAM/X.509, TLS обязателен. Azure SQL: endpoint FQDN сервера + межсетевой экран/Private Link + аутентификация Microsoft Entra ID (ранее Azure AD) + TLS обязателен. Правило: используйте в строке подключения endpoint, а не фиксированный IP, требуйте TLS и по возможности подключайтесь с облачной IAM-идентичностью."}],"related":["last-backup-status","promote-failover","connect-cli"]}]},"playbooks/automation.json":{"schemaVersion":1,"playbooks":[{"id":"auto-ansible","title":"Автоматизация баз данных с Ansible","icon":"🤖","symptom":"Цель: сделать задачи установки, настройки, управления пользователями и резервного копирования баз данных воспроизводимыми и идемпотентными (повторный запуск того же playbook не ломает систему) с помощью Ansible.","steps":[{"title":"Определите inventory и группы","detail":"Группируйте серверы в inventory по роли базы данных; размещайте primary-, replica- и backup-серверы в отдельных группах. Используйте статический INI/YAML inventory или динамический inventory для облака (aws_ec2, gcp_compute). Переменные групп размещайте в group_vars/, а переменные отдельных хостов — в 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":"Установите нужную коллекцию (community.postgresql / community.mysql / community.mongodb)","detail":"Ansible core теперь поставляет модули для баз данных в отдельных коллекциях; установите коллекцию для вашего движка через requirements.yml. Используйте community.postgresql (postgresql_db, postgresql_user, postgresql_privs) для PostgreSQL, community.mysql (mysql_db, mysql_user) для MySQL/MariaDB и community.mongodb для MongoDB. На целевом хосте должен быть установлен соответствующий Python-клиент (psycopg2, PyMySQL, pymongo).","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":"Шифруйте секреты с Ansible Vault","detail":"Не оставляйте пароли баз данных, секреты репликации и учётные данные суперпользователя открытым текстом; храните их в зашифрованном файле переменных с ansible-vault. Расшифровывайте во время выполнения с --ask-vault-pass или --vault-password-file. Тогда секреты можно безопасно хранить в Git, а playbook остаётся идемпотентным.","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":"Создайте структуру ролей (roles/postgres, roles/mysql)","detail":"Создайте по одной роли на каждый движок и разделите логику по подкаталогам tasks/, defaults/, templates/ и handlers/. Такая структура повышает переиспользуемость: defaults/main.yml — для значений по умолчанию, templates/ — для шаблонов конфигурации jinja2, handlers/ — для логики «перезапуск, только если что-то изменилось». Каркас роли можно создать командой 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":"Пишите задачи установки и настройки идемпотентно","detail":"Установите пакет, инициализируйте каталог данных и сформируйте postgresql.conf/my.cnf из шаблона; при изменении конфигурации перезапускайте сервис через handler. Модули идемпотентны: уже установленный пакет не переустанавливается, а handler не срабатывает, если файл не изменился. Формируйте шаблон конфигурации модулем template и связывайте его с handler через '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":"Определите задачи для баз данных и пользователей","detail":"Создавайте базу данных, пользователей и привилегии с помощью модулей; пароль берите из переменной Vault. Модули community.postgresql идемпотентны: если пользователь/БД существует, они не создают его заново, а применяют только разницу. Привилегии выдавайте отдельно через postgresql_privs; в MySQL используется поле priv модуля mysql_user.","engines":{"postgres":"- name: application database\n  community.postgresql.postgresql_db:\n    name: appdb\n    state: present\n\n- name: application user\n  community.postgresql.postgresql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    db: appdb\n    state: present","mysql":"- name: application user\n  community.mysql.mysql_user:\n    name: app_user\n    password: \"{{ vault_app_password }}\"\n    priv: 'appdb.*:ALL'\n    state: present"}},{"title":"Добавьте задачу бэкапа и расписание","detail":"Разверните скрипт бэкапа из шаблона и запланируйте его через ansible.builtin.cron; тогда задание бэкапа тоже управляется как код. Вынесите в переменные целевой каталог бэкапа, срок хранения и расписание. Модуль cron идемпотентен и определяет запись по 'name', поэтому повторный запуск не создаёт дубликат.","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":"Развёртывание баз данных с Terraform (IaC)","icon":"🏗️","symptom":"Цель: описать инфраструктуру баз данных (экземпляр, группу параметров, сеть) как код (Infrastructure as Code) с помощью Terraform; настроить процесс plan/apply, удалённое состояние (remote state) и управление дрейфом (drift).","steps":[{"title":"Выберите и настройте провайдер (aws / google / azurerm)","detail":"Определите провайдер для используемого облака и зафиксируйте его версию. В блоке required_providers закрепите hashicorp/aws для AWS, hashicorp/google для GCP и hashicorp/azurerm для Azure. Фиксация версии обеспечивает одинаковое поведение во всей команде.","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":"Настройте удалённое состояние и блокировку","detail":"Не храните файл состояния локально; для командной работы и безопасности используйте удалённый backend. Типичные варианты: backend S3 + таблица блокировок DynamoDB в AWS, backend gcs в GCP и backend azurerm (Storage Account) в Azure. Удалённое состояние предотвращает параллельные apply за счёт блокировки и делает состояние единым источником истины. Поскольку пароли могут храниться в состоянии открытым текстом, держите backend зашифрованным и с ограниченным доступом.","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":"Определите ресурс базы данных","detail":"Опишите основной ресурс для вашего движка и облака: aws_db_instance в AWS (или aws_rds_cluster для Aurora), google_sql_database_instance в GCP, azurerm_mssql_database в Azure (для SQL Server — внутри azurerm_mssql_server). Здесь задайте движок, версию, класс, хранилище и окно бэкапа.","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":"Опишите группу параметров как код","detail":"Задавайте настройки базы данных (max_connections, work_mem, кодировка) ресурсом параметров, а не вручную в консоли. Используйте aws_db_parameter_group в AWS, settings.database_flags внутри google_sql_database_instance в GCP и ресурс конфигурации сервера в Azure. Тогда настройки версионируются и воспроизводимы.","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":"Передавайте секреты через переменные и менеджер секретов","detail":"Не прописывайте пароль в файле .tf; определите его как чувствительную переменную (variable ... sensitive = true) и передавайте значение через переменную окружения TF_VAR_, менеджер секретов (AWS Secrets Manager, Vault) или секрет CI. Помните: значение записывается в файл состояния, поэтому удалённое состояние должно быть зашифровано. Самый чистый подход — вообще не передавать пароль в Terraform, а читать его из ресурса менеджера секретов.","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":"Выполните процесс plan и apply","detail":"Сначала подготовьте провайдер/backend командой terraform init, посмотрите, что изменится, через terraform plan, утвердите и примените через terraform apply. Не применяйте без просмотра вывода plan; безопасный процесс в CI — публиковать 'plan' комментарием к PR и применять после утверждения человеком. Деструктивные изменения (например, пересоздание экземпляра) выявляйте по маркеру -/+ в выводе plan.","engines":{"postgres":"terraform init\nterraform plan -out=plan.tfplan\nterraform apply plan.tfplan"}},{"title":"Обнаружение и устранение дрейфа","detail":"Если кто-то вручную вносит изменение в консоли, реальная инфраструктура и состояние расходятся (drift). Различия, появляющиеся в terraform plan, «когда ничего не должно было измениться», выявляют дрейф; разница читается через terraform refresh/plan. Либо перенесите изменение обратно в код, либо верните инфраструктуру к коду через apply. Запретите ручные изменения как правило; код должен быть единым источником истины.","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":"Базы данных в Kubernetes (оператор)","icon":"☸️","symptom":"Цель: запустить базу данных в Kubernetes с помощью оператора; вместо голого StatefulSet использовать предоставляемые оператором автоматическую отработку отказа, постоянное хранилище и бэкапы.","steps":[{"title":"Зачем оператор: одного StatefulSet недостаточно","detail":"Обычный StatefulSet даёт поды со стабильными идентификаторами и PVC, но не логику отработки отказа, настройки репликации, бэкапа и обновлений. Оператор упаковывает эти специфичные для БД эксплуатационные знания в контроллер + пользовательский ресурс (CRD). Вы пишете CR под названием 'Cluster', а оператор управляет стоящими за ним StatefulSet, Service, Secret и заданиями бэкапа. В production запуск баз данных через оператор, как правило, предпочтителен.","engines":{"postgres":"# operators for PostgreSQL:\n#  - CloudNativePG (CNPG)\n#  - Zalando postgres-operator\n#  - Crunchy PGO"}},{"title":"Выберите и установите оператор","detail":"Выберите оператор для вашего движка: CloudNativePG или Zalando postgres-operator для PostgreSQL; для MySQL и MongoDB распространены операторы Percona (Percona Operator for MySQL / for MongoDB). Установите оператор через Helm или bundle-манифест; этот шаг добавляет в кластер CRD и под контроллера.","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":"Спланируйте хранилище (StorageClass / PVC)","detail":"Состояние базы данных хранится в PVC; укажите используемый StorageClass в CR. Выбирайте StorageClass с блочным хранилищем низкой задержки, в идеале локальным SSD; сетевые файловые системы (NFS) для большинства баз данных не рекомендуются. Сразу правильно задайте размер PVC и возможность расширения (allowVolumeExpansion), потому что уменьшить его сложно.","engines":{"postgres":"spec:\n  storage:\n    storageClass: fast-ssd\n    size: 50Gi"}},{"title":"Примените Cluster CR (реплики и автоматическая отработка отказа)","detail":"Укажите в CR число экземпляров/реплик; оператор настраивает синхронную/асинхронную репликацию и при сбое primary выполняет автоматическую отработку отказа, повышая реплику. Приложение должно подключаться к Service '-rw' (read-write), созданному оператором; этот сервис всегда указывает на текущий primary. Поэтому цель подключения при отработке отказа не меняется.","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":"Запись бэкапов в S3 через CR","detail":"Операторы также управляют бэкапами декларативно: в CR задаётся основное место назначения бэкапов (объектное хранилище/S3) и архивирование WAL. В CloudNativePG параметры S3 задаются через spec.backup.barmanObjectStore (устарело с 1.26; для новых развёртываний используйте Barman Cloud Plugin и его ресурс ObjectStore); непрерывное архивирование WAL + базовые бэкапы обеспечивают восстановление на момент времени (PITR). Учётные данные берите по ссылке из 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":"CR для бэкапа по расписанию и восстановления","detail":"Создайте Backup CR для разового бэкапа и ScheduledBackup CR для бэкапов по расписанию; регулярно делайте базовые бэкапы по cron-выражению. Восстановление также декларативно: новый Cluster разворачивается из существующего бэкапа в объектном хранилище через 'bootstrap.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":"CI/CD миграций схемы (Flyway/Liquibase)","icon":"🧬","symptom":"Цель: управлять изменениями схемы базы данных с помощью версионированных файлов миграций; применять их автоматически, но контролируемо в CI/CD с помощью Flyway или Liquibase.","steps":[{"title":"Создайте версионированные файлы миграций (Vxxx__name.sql)","detail":"Записывайте каждое изменение схемы в именованный файл с возрастающим номером версии; соглашение Flyway — V<version>__<description>.sql (например, V1__initial_schemas.sql, V2__orders_table.sql). Эти файлы хранятся в Git и применяются в порядке версий. Применённые миграции фиксируются в служебной таблице (flyway_schema_history в Flyway), поэтому каждая миграция выполняется ровно один раз.","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":"Обеспечьте неизменность с помощью контрольных сумм","detail":"Flyway/Liquibase хранят контрольную сумму каждой применённой миграции. Если позже отредактировать уже применённый файл, контрольная сумма перестаёт совпадать, и инструмент завершается с ошибкой. Правило: НИКОГДА не редактируйте применённую миграцию; исправление вносите новым файлом версии. Это гарантирует, что у всех окружений (dev/staging/prod) одинаковая история.","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 для существующей базы данных","detail":"Используйте baseline при подключении инструмента миграций к уже заполненной базе данных без истории; он считает текущее состояние «начальной версией» и не пытается заново создавать существующие объекты. Применяются миграции после baselineVersion. В окружении, создаваемом с нуля, baseline не нужен; миграции выполняются начиная с V1.","engines":{"postgres":"flyway baseline -baselineVersion=1 -baselineDescription=\"existing schema\""}},{"title":"Автоматическая миграция в CI-конвейере","detail":"Запускайте инструмент миграций на шаге CI, чтобы автоматически применять изменения к тестовой и staging базам данных; команда migrate выполняет только новые (неприменённые) файлы и идемпотентна. Предварительное применение миграции к временной (эфемерной) базе данных с проверкой выявляет ошибочный SQL до попадания в prod. Схема должна мигрироваться ДО развёртывания кода приложения (с обратно совместимыми изменениями).","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":"Добавьте ручное утверждение для prod","detail":"Test/staging — автоматически, но migrate в prod должен требовать ручного утверждения; добавьте в CI/CD шаг 'approval gate' (ручное утверждение). Перед утверждением покажите, какие миграции будут выполнены, через 'flyway info' или 'liquibase status'. Перед применением в prod всегда делайте свежий бэкап, чтобы откатиться в случае проблем.","engines":{"postgres":"# what will run before approval?\nflyway info\n# liquibase equivalent:\nliquibase status --verbose"}},{"title":"Определите обратимость и стратегию отката","detail":"Во Flyway Community нет автоматического undo; придерживайтесь принципа миграций только вперёд (forward-only): вместо отмены ошибки пишите новую миграцию, которая её исправляет. Changeset в Liquibase может содержать блок rollback и откатываться через 'liquibase rollback'. Самая надёжная стратегия: делайте изменения небольшими и обратно совместимыми, перед рискованными операциями (удаление столбцов) делайте бэкап и подготовьте письменный план отката.","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":"Автоматические бэкапы (pgBackRest/Barman/Xtrabackup/cron)","icon":"💾","symptom":"Цель: автоматизировать бэкапы баз данных; построить надёжную цепочку восстановления с плановыми full+incremental бэкапами и архивированием WAL/binlog, хранением (retention), проверкой и внешней копией.","steps":[{"title":"Выберите подходящий инструмент бэкапа","detail":"Выберите инструмент для вашего движка: pgBackRest или Barman для PostgreSQL (физический бэкап + архив WAL + PITR); Percona XtraBackup (онлайн физический бэкап) для MySQL/MariaDB или mysqldump/mydumper (логический) для небольших объёмов данных. Физические инструменты дают быстрые согласованные бэкапы + восстановление на момент времени для больших баз; логические дампы переносимы, но медленны на больших данных.","engines":{"postgres":"# pgBackRest or Barman","mysql":"# physical: xtrabackup\n# logical: mysqldump / mydumper"}},{"title":"Постройте цепочку full + incremental + непрерывный архив","detail":"Настройте восстановление на момент времени с еженедельными/ежедневными полными бэкапами, инкрементальными/дифференциальными между ними и непрерывным архивированием WAL (PostgreSQL) или binlog (MySQL). pgBackRest управляет типами бэкапа full/diff/incremental и архивом WAL; в PostgreSQL archive_command указывает на pgBackRest. Инкрементальные бэкапы + архив позволяют вернуться к любому моменту времени.","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":"Планирование бэкапов через cron/timer","detail":"Регулярно запускайте команды бэкапа через cron или systemd timer: например, полный бэкап в ночь на воскресенье и инкрементальные в остальные ночи. Планируйте вне рабочего времени и следите, чтобы задания full и incr не пересекались. Записывайте логи вывода и поднимайте оповещение при сбое.","engines":{"postgres":"# crontab\n0 1 * * 0 pgbackrest --stanza=appdb --type=full backup\n0 1 * * 1-6 pgbackrest --stanza=appdb --type=incr backup"}},{"title":"Определите политику хранения","detail":"Не накапливайте бэкапы бесконечно; задайте, сколько полных бэкапов и сколько дней архивов хранить, и пусть инструмент автоматически удаляет устаревшие. В pgBackRest это управляется через repo1-retention-full и настройки хранения архива; в Barman — через retention_policy. Хранение должно соответствовать целям восстановления (на сколько дней назад нужно уметь вернуться).","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":"Копируйте бэкапы на внешнюю площадку / в S3","detail":"Бэкапы не должны оставаться на той же машине/в том же регионе, что и база данных; копируйте их в другое место или объектное хранилище (S3, GCS). pgBackRest может писать напрямую в S3-совместимое хранилище; иначе синхронизируйте с внешней площадкой через rsync/rclone. При аварии (потеря локального диска/региона) спасёт только внешняя копия.","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":"Проверьте бэкап","detail":"Наличие бэкапа не означает, что его можно восстановить; регулярно проверяйте целостность. В pgBackRest команда 'verify' и проверки согласованности архива, а в XtraBackup успешный шаг prepare (--prepare) показывают, что бэкап исправен. Запускайте проверку автоматически после каждого бэкапа и поднимайте тревогу при сбое.","engines":{"postgres":"pgbackrest --stanza=appdb verify","mysql":"# make the backup consistent (verification)\nxtrabackup --prepare --target-dir=/backup/full"}},{"title":"Регулярно проводите учения по восстановлению","detail":"Самый важный шаг: периодически ДЕЙСТВИТЕЛЬНО восстанавливайте бэкап на отдельный сервер и проверяйте, что база данных открывается и данные согласованы. Такие учения позволяют измерить время восстановления (RTO) и найти пробелы в процедуре вне инцидента. Непроверенный бэкап — не бэкап.","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 / развёртывание без простоя","icon":"🟦","symptom":"Цель: развёртывать изменения схемы и приложения без простоя; приблизиться к нулевому простою с помощью миграций expand-contract, окружений blue-green, feature flags и автоматического отката.","steps":[{"title":"GitOps: сделайте Git единым источником истины","detail":"Описывайте инфраструктуру, миграции схемы и версию приложения в Git; развёртывание запускается слиянием в репозиторий (GitOps). Reconciler (Argo CD/Flux или CI/CD) применяет желаемое состояние к реальному окружению. Тогда каждое изменение проверяемо, воспроизводимо и при необходимости может быть откачено к предыдущему коммиту.","engines":{"postgres":"# repo/\n#   migrations/  (Vxxx__*.sql)\n#   k8s/         (Deployment, Service)\n#   flags/       (feature flag config)"}},{"title":"Меняйте схему обратно совместимо (expand-contract)","detail":"Для нулевого простоя не меняйте схему одним ломающим шагом; разделите изменение на две фазы. EXPAND: добавьте новую структуру (новый столбец/таблицу) так, чтобы старый и новый код могли работать одновременно (nullable-столбец, со значением по умолчанию, двойная запись). ПОСЛЕ перехода приложения на новую структуру — CONTRACT: удалите больше не используемый старый столбец/таблицу отдельной миграцией. Тогда старая и новая версии приложения во время развёртывания без проблем работают параллельно.","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":"Спрячьте рискованное поведение за feature flag","detail":"Запускайте новый путь выполнения кода (например, чтение нового столбца) условно за feature flag; включайте флаг, когда схема готова, и выключайте без изменения развёртывания, если что-то пошло не так. Флаг отделяет развёртывание кода (deploy) от активации функции (release). Фаза expand выкатывается, но изменение поведения включается контролируемо и может быть быстро отменено.","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":"Настройте окружение blue-green","detail":"Держите два идентичных окружения: 'blue' (рабочее) и 'green' (новая версия). Разверните новую версию приложения в green и проверьте её до получения реального трафика; когда всё готово, переключите маршрутизатор/трафик на green. Важно: база данных обычно ОБЩАЯ, поэтому схема должна быть совместима с обеими версиями (это обеспечивает expand-contract). Хотя цель подключения при переключении меняется, совместимость схемы означает отсутствие простоя.","engines":{"postgres":"# traffic is switched to green (new version)\n# shared DB: schema must be compatible with both blue and green"}},{"title":"Проверяйте развёртывание мониторингом (canary/наблюдение)","detail":"Прежде чем переключать весь трафик, отслеживайте такие метрики, как доля ошибок, задержка, число ошибок/блокировок в базе данных и отставание репликации; при желании направьте небольшой процент трафика (canary) на green и понаблюдайте. Если показатели работоспособности хорошие, увеличивайте трафик постепенно; если плохие — не продолжайте. Мониторинг служит входными данными для решения об автоматическом откате.","engines":{"postgres":"-- live health queries\nSELECT count(*) FROM pg_stat_activity WHERE wait_event_type='Lock';\nSELECT * FROM pg_stat_replication;"}},{"title":"Подготовьте автоматический откат","detail":"Если метрики превышают порог, развёртывание должно откатываться автоматически: в blue-green быстро переключите трафик обратно на blue (старую версию). Откат приложения мгновенный, потому что blue всё ещё работает. Со стороны схемы откат обычно НЕ НУЖЕН: благодаря expand-contract новая схема совместима и со старым кодом, поэтому достаточно откатить приложение. Именно поэтому изменения схемы должны оставаться независимыми от развёртывания приложения и всегда быть обратно совместимыми.","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"]}]}}