pg_reports 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +143 -378
  4. data/app/controllers/pg_reports/dashboard_controller.rb +21 -21
  5. data/app/views/layouts/pg_reports/application.html.erb +65 -8
  6. data/app/views/pg_reports/dashboard/_show_modals.html.erb +22 -22
  7. data/app/views/pg_reports/dashboard/_show_scripts.html.erb +55 -57
  8. data/app/views/pg_reports/dashboard/_show_styles.html.erb +18 -0
  9. data/app/views/pg_reports/dashboard/index.html.erb +109 -106
  10. data/app/views/pg_reports/dashboard/show.html.erb +26 -26
  11. data/config/locales/en.yml +488 -0
  12. data/config/locales/ru.yml +481 -0
  13. data/config/locales/uk.yml +481 -0
  14. data/lib/pg_reports/annotation_parser.rb +13 -1
  15. data/lib/pg_reports/compatibility.rb +3 -3
  16. data/lib/pg_reports/dashboard/reports_registry.rb +83 -12
  17. data/lib/pg_reports/definitions/schema_analysis/always_null_columns.yml +31 -0
  18. data/lib/pg_reports/definitions/schema_analysis/unused_columns.yml +32 -0
  19. data/lib/pg_reports/definitions/tables/unused_tables.yml +30 -0
  20. data/lib/pg_reports/definitions/tables/update_hotspots.yml +32 -0
  21. data/lib/pg_reports/module_generator.rb +2 -1
  22. data/lib/pg_reports/modules/schema_analysis.rb +261 -2
  23. data/lib/pg_reports/modules/system.rb +3 -3
  24. data/lib/pg_reports/query_monitor.rb +2 -6
  25. data/lib/pg_reports/report_definition.rb +20 -24
  26. data/lib/pg_reports/sql/schema_analysis/always_null_columns.sql +25 -0
  27. data/lib/pg_reports/sql/schema_analysis/unused_columns.sql +36 -0
  28. data/lib/pg_reports/sql/tables/unused_tables.sql +19 -0
  29. data/lib/pg_reports/sql/tables/update_hotspots.sql +26 -0
  30. data/lib/pg_reports/version.rb +1 -1
  31. metadata +9 -1
@@ -244,6 +244,29 @@ ru:
244
244
  - "Высокая активность UPDATE создаёт dead tuples — следите за vacuum."
245
245
  - "HOT updates (Heap Only Tuple) эффективнее обычных — индексы не обновляются."
246
246
 
247
+ update_hotspots:
248
+ title: "Горячие точки UPDATE"
249
+ what: "Таблицы, где одни и те же строки обновляются многократно, либо где индексированные колонки получают hot-update."
250
+ how: "Считает два соотношения из pg_stat_user_tables: updates_per_row = n_tup_upd / n_live_tup (амплификация записи на строку) и hot_update_pct = n_tup_hot_upd / n_tup_upd (низкое значение = индексированная колонка попадает в SET, что ломает HOT)."
251
+ nuances:
252
+ - "updates_per_row >> 1 означает, что отдельные строки переписываются многократно — типично для счётчиков, статусов, last_seen_at, позиций."
253
+ - "Паттерны рефакторинга для горячих строк: вынести изменчивую колонку в соседнюю 1:1 'state'-таблицу; перейти на event/log-таблицу с периодическими свёртками; буферизировать записи в фоне; debounce на стороне приложения."
254
+ - "Низкий hot_update_pct (например, <50%) = обновляется индексированная колонка. Либо удалите индекс, либо вынесите колонку в менее индексированную таблицу."
255
+ - "PostgreSQL не считает UPDATE по колонкам. Чтобы найти виновную колонку, парсите pg_stat_statements: имена колонок в SET сохраняются (нормализуются только литералы)."
256
+ - "Горячие строки + fillfactor по умолчанию (100) убивают HOT. ALTER TABLE ... SET (fillfactor=80) оставляет место для in-place UPDATE."
257
+ - "Высокий n_dead_tup коррелирует с горячими строками — autovacuum должен успевать, иначе bloat будет расти быстро."
258
+
259
+ unused_tables:
260
+ title: "Неиспользуемые таблицы"
261
+ what: "Таблицы с нулём seq_scan и нулём idx_scan с момента последнего сброса статистики — код их не читает."
262
+ how: "Фильтрует pg_stat_user_tables по seq_scan = 0 AND idx_scan = 0. Показывает db_stats_since (pg_stat_database.stats_reset), чтобы было понятно, на каком окне сделан вывод."
263
+ nuances:
264
+ - "Убедитесь, что db_stats_since покрывает репрезентативный период — минимум неделя, лучше полный отчётный/биллинговый цикл. Недавний stats_reset, рестарт или обновление PG обнуляют выводы."
265
+ - "Таблицы, читаемые только репликами, НЕ считаются на праймари. Проверьте реплики отдельно перед удалением."
266
+ - "Таблицы, к которым обращаются только COPY, pg_dump или logical replication, могут выглядеть unused — эти операции не инкрементят счётчики сканов."
267
+ - "FK на эту таблицу могут держать её 'в работе' через каскады без прямых запросов."
268
+ - "Безопасная последовательность: переименовать → подождать полный цикл → удалить. Или переместить в отдельную archive-схему."
269
+
247
270
  # === CONNECTIONS ===
248
271
  active_connections:
249
272
  title: "Active Connections"
@@ -410,6 +433,74 @@ ru:
410
433
  - "Низкий cache hit: увеличьте shared_buffers (до 25% RAM), или проблема в запросах."
411
434
 
412
435
  # === SCHEMA ANALYSIS ===
436
+ unused_columns:
437
+ title: "Неиспользуемые колонки"
438
+ what: "Колонки, которые когда-либо хранили только одно уникальное значение — сильный признак, что приложение их больше не обновляет."
439
+ how: "Читает pg_stats.n_distinct = 1 (одно значение по выборке) и джойнит pg_attrdef для отображения default. Исключает первичные ключи и колонки уникальных индексов, чтобы убрать шум."
440
+ nuances:
441
+ - "n_distinct — оценка по выборке. Сделайте ANALYZE перед тем, как доверять результату. Устаревшая статистика даёт ложные срабатывания."
442
+ - "Таблицы меньше ~1000 строк исключены — на маленькой выборке одно значение статистически бессмысленно."
443
+ - "Ложные срабатывания: feature flags, enum-колонки с одним released-значением, колонки с дефолтом и одним валидным вариантом."
444
+ - "Истинные находки: колонки, у которых удалили Ruby/ORM-аксессор, но миграцию на drop column не написали; статусы, заменённые другим механизмом, но оставленные в схеме."
445
+ - "PostgreSQL не отслеживает запись по колонкам, это эвристика. Перед удалением grep'ните кодовую базу по имени колонки."
446
+ - "Смотрите также `always_null_columns` — тот же диагноз с другого ракурса."
447
+
448
+ always_null_columns:
449
+ title: "Колонки, всегда NULL"
450
+ what: "Nullable-колонки, где 100% строк = NULL — приложение перестало (или никогда не начинало) их писать."
451
+ how: "Читает pg_stats.null_frac >= 0.999 (фактически все NULL) и исключает колонки с NOT NULL. Джойнит pg_attrdef, чтобы показать оставшийся default."
452
+ nuances:
453
+ - "null_frac — оценка по выборке. Сделайте ANALYZE, если сомневаетесь. Перепроверьте после репрезентативного окна нагрузки."
454
+ - "Колонка может быть NULL, потому что путь записи редко срабатывает (premium-only поля, opt-in фичи). Проверяйте перед удалением."
455
+ - "Если null_pct = 100 И есть не-NULL default — default не используется, удаляйте вместе с колонкой."
456
+ - "Некоторые ORM сериализуют пустые строки как '' вместо NULL; этот отчёт их не поймает. Если нужен такой сигнал — отдельный отчёт."
457
+ - "Сравните с `unused_columns` — он ловит колонки с одним значением, этот — все-NULL. Вместе покрывают основные паттерны мёртвых колонок."
458
+
459
+ polymorphic_without_index:
460
+ title: "Polymorphic без индекса"
461
+ what: "Полиморфные `belongs_to`-ассоциации, у которых пара `(*_type, *_id)` не покрыта составным индексом."
462
+ how: "Обходит `ActiveRecord::Base.descendants`, собирает `belongs_to`-рефлексии с `polymorphic: true` и проверяет `pg_index` на наличие индекса, покрывающего обе колонки."
463
+ nuances:
464
+ - "PostgreSQL не сможет использовать два одиночных индекса так же эффективно, как один составной для `WHERE x_type = ? AND x_id = ?` — это базовый паттерн загрузки полиморфной ассоциации."
465
+ - "Подсказка `coverage` уточняет пробел: \"neither indexed\" / \"only id indexed\" / \"only type indexed\" / \"type and id indexed separately\". Последние два — частичные решения, не полные."
466
+ - "На маленьких таблицах безвредно; на больших (Comment, Note, Activity, AuditLog) каждый запрос к ассоциации превращается в seq scan."
467
+ - "Предлагаемая миграция использует порядок `(type, id)` — у type обычно ниже cardinality, индекс сужается быстрее."
468
+ - "Перед обходом моделей выполняется `eager_load!`, чтобы в dev результат был полным."
469
+
470
+ counter_cache_issues:
471
+ title: "Проблемы counter_cache"
472
+ what: "`belongs_to ..., counter_cache: ...` декларации, у которых целевая колонка отсутствует в родительской таблице."
473
+ how: "Обходит все модели, находит `belongs_to` с опцией `counter_cache`, разрешает имя ожидаемой колонки (`<child_table>_count` для `counter_cache: true` или явный символ/строка) и проверяет колонки родителя."
474
+ nuances:
475
+ - "Отсутствующая колонка = counter молча сломан: записи никуда не идут, `parent.<assoc>_count` возвращает nil, любой код, читающий cached-значение, получает stale или ноль."
476
+ - "Обратное направление (колонка `*_count` без декларации `counter_cache`) намеренно не флагается — слишком много false positives для вручную поддерживаемых счётчиков."
477
+ - "Родительский класс резолвится через `assoc.klass`; если класс отсутствует или не загружается — строка пропускается."
478
+ - "Имя по умолчанию для `counter_cache: true`: на родителя добавляется колонка с именем по таблице **дочерней** модели в множественном числе + `_count`. Пример: `Comment belongs_to :user, counter_cache: true` → `users.comments_count`."
479
+ - "Перед обходом моделей выполняется `eager_load!`, чтобы в dev результат был полным."
480
+
481
+ soft_delete_without_scope:
482
+ title: "Soft delete без scope"
483
+ what: "Таблицы с колонкой `deleted_at` / `discarded_at` / `archived_at`, у модели которых нет scope, фильтрующего soft-deleted строки."
484
+ how: "Для каждой таблицы проверяется наличие канонических имён soft-delete колонок. Если есть — находится модель и проверяется `acts_as_paranoid` (paranoia), `discard_column` (discard) или `default_scope`, чей сгенерированный SQL ссылается на колонку."
485
+ nuances:
486
+ - "Без default scope любой обычный `Model.where(...)` возвращает soft-deleted строки. Это тихо протекает в отчёты, индексы, поиск, экспорты."
487
+ - "Если команда сознательно отказалась от default scope (предпочитая явные `kept`/`with_deleted`), отчёт даст по строке на каждую такую модель — это intentional, можно игнорировать."
488
+ - "Метод детекции default_scope: строит `model.all.to_sql` и ищет имя колонки. Ловит `default_scope { where(deleted_at: nil) }` и эквиваленты, но пропускает scope из concerns, которые ещё не загружены."
489
+ - "Таблицы без модели рапортуются отдельно со статусом `no_model` — обычно значит, что таблица используется только raw SQL и требует ручного ревью."
490
+ - "Перед обходом моделей выполняется `eager_load!`, чтобы в dev результат был полным."
491
+
492
+ orphan_tables:
493
+ title: "Orphan-таблицы"
494
+ what: "Таблицы БД без соответствующего Rails-класса модели."
495
+ how: "Перечисляет все таблицы из `pg_class` (за исключением `schema_migrations` и `ar_internal_metadata`) и пытается найти модель по обычным naming conventions. Таблицы, не находящие модель, попадают в отчёт."
496
+ nuances:
497
+ - "Три классификации: `join_table_candidate` (ровно две `*_id` колонки и больше ничего — вероятно, легитимная HABTM-таблица), `join_model_without_class` (несколько FK + дополнительные поля — вероятно, должна была быть моделью join'а), `legacy` (всё остальное)."
498
+ - "`join_table_candidate` обычно intentional — Rails не требует модель для HABTM. Просмотреть и игнорировать."
499
+ - "`legacy` — самые интересные строки: таблицы, созданные до того, как добавили модель; переименованные-но-не-удалённые; созданные мимо Rails другим сервисом; принадлежащие удалённой фиче."
500
+ - "Сверьте с `unused_tables` (категория Таблицы) — таблица одновременно orphan И с нулём чтений = сильный кандидат на удаление."
501
+ - "False positives: namespaced-модели (`Admin::User` → `admin_users`), STI-подклассы на одной базовой таблице, модели в engines, которые не eager-loadятся."
502
+ - "Row count приблизительный (n_live_tup из pg_stat_user_tables) — недавно записанные таблицы могут показывать 0 до следующего ANALYZE."
503
+
413
504
  missing_validations:
414
505
  title: "Отсутствующие валидации"
415
506
  what: "Уникальные индексы в базе данных без соответствующих валидаций uniqueness в Rails-моделях."
@@ -447,3 +538,393 @@ ru:
447
538
  pool_saturation: "Пул соединений насыщен. Риск исчерпания соединений и ошибок приложения."
448
539
  high_connection_churn: "Высокая оборачиваемость соединений. Внедрите connection pooling для снижения накладных расходов."
449
540
  too_many_short_connections: "Слишком много короткоживущих соединений. Приложение должно переиспользовать соединения через pooling."
541
+ unused_column: "Колонка имеет одно значение во всех строках. Скорее всего, не обновлялась с момента создания — кандидат на удаление."
542
+ always_null_column: "Колонка на 100% NULL. Приложение больше не пишет в это поле — кандидат на удаление."
543
+ hot_rows: "Одни и те же строки обновляются многократно. Рассмотрите разделение горячих/холодных колонок, батчинг записи или event-log таблицу."
544
+ low_hot_update: "Низкий процент HOT updates — обновляются индексированные колонки. Удалите индекс или вынесите колонку, чтобы снизить write amplification."
545
+ unused_table: "Таблица не читалась с момента последнего сброса статистики. Кандидат на архивацию или удаление — сначала проверьте окно статистики."
546
+ polymorphic_no_index: "Полиморфная ассоциация без составного индекса (type, id). С ростом таблицы запросы будут seq-scan'ить."
547
+ counter_cache_missing_column: "Колонка counter_cache отсутствует в родительской таблице. Counter молча сломан — записи никуда не идут."
548
+ soft_delete_unprotected: "Soft-delete колонка без scope, фильтрующего её. Обычные запросы возвращают удалённые строки."
549
+ orphan_table_legacy: "Таблица без Rails-модели. Скорее всего, legacy — проверьте перед удалением."
550
+
551
+ # UI strings shown in the dashboard chrome (buttons, modals, toasts, etc.)
552
+ ui:
553
+ branding:
554
+ title: "PgReports"
555
+ subtitle: "Дашборд анализа PostgreSQL"
556
+ page_title: "PgReports — Дашборд"
557
+ navigation:
558
+ dashboard: "Дашборд"
559
+ back: "← Назад"
560
+ actions:
561
+ cancel: "Отмена"
562
+ retry: "Повторить"
563
+ copy: "📋 Копировать"
564
+ copy_query: "📋 Копировать запрос"
565
+ copy_code: "📋 Копировать код"
566
+ copy_to_clipboard_title: "Скопировать в буфер обмена"
567
+ copied_feedback: "✓ Скопировано!"
568
+ clear_all: "Очистить всё"
569
+ run_report: "▶ Запустить отчёт"
570
+ export: "⬇ Экспорт"
571
+ download_text: "📄 Текст (.txt)"
572
+ download_csv: "📊 CSV (.csv)"
573
+ download_json: "📋 JSON (.json)"
574
+ download: "📥 Скачать"
575
+ copy_ai_prompt: "Копировать промпт"
576
+ send_telegram: "📨 Telegram"
577
+ sending: "Отправка..."
578
+ reset_statistics: "🗑️ Сбросить статистику"
579
+ resetting: "Сброс..."
580
+ confirm_reset: "Да, сбросить"
581
+ create_extension: "⚡ Создать расширение"
582
+ creating: "Создание..."
583
+ ide_settings_button_title: "Настройки IDE"
584
+ explain_analyze: "📊 EXPLAIN ANALYZE"
585
+ execute_query: "▶ Выполнить запрос"
586
+ create_migration_file: "📁 Создать файл и открыть в IDE"
587
+ start_monitoring: "▶ Запустить мониторинг"
588
+ stop_monitoring: "⏹ Остановить мониторинг"
589
+ starting: "Запуск..."
590
+ stopping: "Остановка..."
591
+ load_history: "📜 Загрузить историю (50)"
592
+ loading: "Загрузка..."
593
+ running: "Выполнение..."
594
+ save_for_comparison: "📌 Сохранить для сравнения"
595
+ saved_marker: "📌 Сохранено"
596
+ status:
597
+ pg_stat_ready: "pg_stat_statements готов"
598
+ extension_installed: "Расширение установлено, не предзагружено"
599
+ preloaded: "Предзагружено, расширение не создано"
600
+ not_configured: "Не настроено"
601
+ monitoring_unavailable: "Live-мониторинг недоступен"
602
+ modals:
603
+ enable_pg_stat_title: "Включение pg_stat_statements"
604
+ enable_pg_stat_intro: "Чтобы включить pg_stat_statements, выполните следующие шаги:"
605
+ edit_postgresql_conf: "Отредактируйте postgresql.conf:"
606
+ restart_postgresql: "Перезапустите PostgreSQL:"
607
+ create_extension_step: "Создайте расширение:"
608
+ enable_button_note: "Или нажмите кнопку «Создать расширение» после перезапуска."
609
+ reset_stats_title: "⚠️ Сброс статистики"
610
+ reset_stats_confirm: "Вы уверены, что хотите сбросить статистику pg_stat_statements?"
611
+ reset_stats_warning: "Это действие очистит всю собранную статистику запросов и не может быть отменено."
612
+ ide_settings_title: "⚙️ Настройки IDE"
613
+ problem_detected_title: "⚠️ Обнаружена проблема"
614
+ query_analyzer_title: "📊 Анализатор запроса"
615
+ query_label: "Запрос:"
616
+ parameters_label: "Параметры:"
617
+ migration_title: "🗑️ Миграция удаления индекса"
618
+ migration_subtitle: "Сгенерированная миграция для удаления индекса:"
619
+ migration_warning: "Создание миграции сгенерирует файл миграции в вашем проекте. Запуск этой миграции удалит индекс из БД, что может значительно повлиять на производительность приложения."
620
+ migration_warning_dev_only: "Эту операцию следует выполнять только в локальном dev-окружении."
621
+ query_execution_disabled_title: "⚠️ Выполнение запросов отключено"
622
+ query_execution_disabled_intro: "Чтобы включить эту функцию, добавьте в конфигурацию:"
623
+ settings:
624
+ default_ide_label: "IDE по умолчанию для ссылок на исходники:"
625
+ ide_show_menu: "Показывать меню (по умолчанию)"
626
+ ide_vscode_wsl: "VS Code (WSL)"
627
+ ide_vscode: "VS Code"
628
+ ide_rubymine: "RubyMine"
629
+ ide_intellij: "IntelliJ IDEA"
630
+ ide_cursor_wsl: "Cursor (WSL)"
631
+ ide_cursor: "Cursor"
632
+ monitoring:
633
+ live_title: "Live-мониторинг"
634
+ update_interval: "Обновление каждые 5с"
635
+ toggle_title: "Переключить live-мониторинг"
636
+ query_monitor_title: "Монитор SQL-запросов"
637
+ session_label: "Сессия:"
638
+ queries_label: "Запросов:"
639
+ feed_empty: "Нажмите «Запустить мониторинг», чтобы начать захват SQL-запросов"
640
+ feed_no_queries: "Запросы пока не захвачены..."
641
+ unknown_source: "Источник неизвестен"
642
+ expand_collapse_title: "Развернуть / свернуть"
643
+ metrics:
644
+ connections_label: "Соединения"
645
+ tps_label: "TPS"
646
+ tps_unit: "тр/с"
647
+ commit_label: "commit:"
648
+ rollback_label: "rollback:"
649
+ cache_hit_label: "Cache hit"
650
+ cache_hit_detail: "блоки heap из кэша"
651
+ long_queries_label: "Долгие запросы"
652
+ queries_unit: "запросов"
653
+ long_running_threshold: "> 60с выполнения"
654
+ blocked_label: "Заблокировано"
655
+ processes_unit: "процессов"
656
+ waiting_for_locks: "ждут блокировок"
657
+ percent_used_suffix: "% использовано"
658
+ categories:
659
+ requires_pg_stat: "🔒 Требуется pg_stat_statements"
660
+ reports_count_suffix: "отчётов"
661
+ documentation:
662
+ toggle_title: "📖 Что показывает этот отчёт?"
663
+ what_section: "📋 Что"
664
+ why_section: "❓ Почему это важно"
665
+ nuances_section: "⚠️ Нюансы"
666
+ thresholds_section: "📊 Пороги"
667
+ threshold_warning_label: "⚠️ Warning:"
668
+ threshold_critical_label: "🔴 Critical:"
669
+ threshold_inverted_note: "(меньше — хуже)"
670
+ filters:
671
+ title: "🔍 Параметры фильтрации"
672
+ current_value: "сейчас"
673
+ saved:
674
+ title: "📌 Сохранено для сравнения"
675
+ saved_at_prefix: "▸ Сохранено:"
676
+ click_to_expand: "Нажмите, чтобы развернуть"
677
+ confirm_clear_all: "Удалить все сохранённые записи для этого отчёта?"
678
+ remove_title: "Удалить"
679
+ results:
680
+ title: "Результаты"
681
+ click_run_hint: "Нажмите «Запустить отчёт», чтобы получить данные"
682
+ empty_message: "Проблем не найдено. Всё хорошо!"
683
+ showing_first_of_total: "Показаны первые %{count} из %{total} строк"
684
+ no_rows_returned: "Нет строк"
685
+ rows_label: "Строк:"
686
+ execution_time_label: "Время выполнения:"
687
+ null_placeholder: "<null>"
688
+ sections:
689
+ recommendation: "💡 Рекомендация"
690
+ detected_issues: "⚠️ Обнаруженные проблемы"
691
+ execution_plan: "📊 План выполнения"
692
+ line_label: "Строка"
693
+ current_label: "Текущее:"
694
+ threshold_label: "Порог:"
695
+ threshold_inverted_long: "(инверсия: меньшие значения — хуже)"
696
+ warning_eq: "warning"
697
+ critical_eq: "critical"
698
+ levels:
699
+ critical: "🔴 Критично"
700
+ warning: "⚠️ Внимание"
701
+ errors:
702
+ error_prefix: "Ошибка:"
703
+ unable_fetch_metrics: "Не удалось получить статистику БД."
704
+ possible_causes: "Возможные причины:"
705
+ cause_permissions: "Недостаточно прав для доступа к БД"
706
+ cause_views: "Системные представления статистики недоступны"
707
+ cause_connection: "Проблемы с соединением"
708
+ fetch_metrics_failed: "Не удалось получить live-метрики"
709
+ fetch_metrics_check_perms: "Не удалось получить статистику БД. Проверьте права доступа."
710
+ insufficient_database_perms: "Недостаточно прав для доступа к системным представлениям статистики"
711
+ network_error_prefix: "Сетевая ошибка:"
712
+ copy_failed: "Не удалось скопировать"
713
+ run_report_first: "Сначала запустите отчёт"
714
+ run_report_failed: "Не удалось запустить отчёт"
715
+ report_not_found: "Отчёт не найден"
716
+ send_telegram_failed: "Не удалось отправить в Telegram"
717
+ reset_stats_failed: "Не удалось сбросить статистику"
718
+ start_monitoring_failed: "Не удалось запустить мониторинг"
719
+ stop_monitoring_failed: "Не удалось остановить мониторинг"
720
+ load_history_failed: "Не удалось загрузить историю:"
721
+ no_query_history: "История запросов не найдена"
722
+ decode_query_failed: "Не удалось декодировать запрос"
723
+ explain_analyze_failed: "Не удалось выполнить EXPLAIN ANALYZE"
724
+ execute_query_failed: "Не удалось выполнить запрос"
725
+ create_migration_failed: "Не удалось создать миграцию"
726
+ explain_disabled_toast: "⚠️ EXPLAIN ANALYZE отключен. Включите в конфигурации: config.allow_raw_query_execution = true"
727
+ execute_disabled_toast: "⚠️ Выполнение запросов отключено. Включите в конфигурации: config.allow_raw_query_execution = true"
728
+ query_monitoring_error: "Ошибка мониторинга запросов"
729
+ query_hash_required: "Требуется хэш запроса"
730
+ query_execution_disabled: "Выполнение запросов из дашборда отключено. Включите в конфигурации: 'config.allow_raw_query_execution = true'"
731
+ query_not_found_expired: "Запрос не найден или истёк срок действия. Обновите страницу."
732
+ security_violation_prefix: "Нарушение безопасности:"
733
+ trigger_variables_not_allowed: "Нельзя выполнить EXPLAIN ANALYZE для запросов с триггерными переменными (NEW, OLD). Они доступны только в контексте триггерных функций."
734
+ missing_parameter_values: "Укажите значения для всех плейсхолдеров параметров ($1, $2 и т.д.)"
735
+ migration_dev_only: "Создание миграций разрешено только в development-окружении"
736
+ filename_code_required: "Имя файла и код обязательны"
737
+ invalid_filename_format: "Неверный формат имени файла миграции"
738
+ migrations_dir_not_found: "Каталог миграций не найден"
739
+ success:
740
+ statistics_reset: "Статистика успешно сброшена"
741
+ report_generated: "Отчёт успешно сгенерирован"
742
+ ai_prompt_copied: "AI-промпт скопирован в буфер обмена"
743
+ explain_copied: "Вывод EXPLAIN скопирован в буфер обмена"
744
+ migration_copied: "Код миграции скопирован в буфер обмена"
745
+ migration_created: "Миграция успешно создана"
746
+ telegram_sent: "Отчёт отправлен в Telegram"
747
+ queries_loaded: "Загружено %{count} запросов из истории"
748
+ record_saved: "Запись сохранена для сравнения"
749
+ record_removed: "Запись удалена"
750
+ record_removed_saved: "Запись удалена из сохранённых"
751
+ all_saved_cleared: "Все сохранённые записи очищены"
752
+
753
+ # Category names (shown on the dashboard grid)
754
+ categories:
755
+ queries: "Запросы"
756
+ indexes: "Индексы"
757
+ tables: "Таблицы"
758
+ connections: "Соединения"
759
+ system: "Система"
760
+ schema_analysis: "Анализ схемы"
761
+
762
+ # Report names and short descriptions (shown on the dashboard listing)
763
+ reports:
764
+ slow_queries:
765
+ name: "Медленные запросы"
766
+ description: "Запросы с высоким средним временем выполнения"
767
+ heavy_queries:
768
+ name: "Частые запросы"
769
+ description: "Самые часто вызываемые запросы"
770
+ expensive_queries:
771
+ name: "Дорогие запросы"
772
+ description: "Запросы с наибольшим суммарным временем"
773
+ missing_index_queries:
774
+ name: "Без индексов"
775
+ description: "Запросы, которым возможно нужны индексы"
776
+ low_cache_hit_queries:
777
+ name: "Низкий cache hit"
778
+ description: "Запросы с плохой утилизацией кэша"
779
+ temp_file_queries:
780
+ name: "Сброс на диск"
781
+ description: "Запросы, сбрасывающие данные на диск"
782
+ all_queries:
783
+ name: "Все запросы"
784
+ description: "Полная статистика запросов"
785
+ unused_indexes:
786
+ name: "Неиспользуемые индексы"
787
+ description: "Индексы, редко или никогда не сканируемые"
788
+ duplicate_indexes:
789
+ name: "Дубликаты индексов"
790
+ description: "Избыточные индексы"
791
+ invalid_indexes:
792
+ name: "Невалидные индексы"
793
+ description: "Индексы, которые не удалось построить"
794
+ missing_indexes:
795
+ name: "Отсутствующие индексы"
796
+ description: "Таблицы, которым возможно нужны индексы"
797
+ inefficient_indexes:
798
+ name: "Неэффективные индексы"
799
+ description: "Индексы с высоким read-to-fetch ratio"
800
+ index_usage:
801
+ name: "Использование индексов"
802
+ description: "Статистика сканирования индексов"
803
+ bloated_indexes:
804
+ name: "Раздутые индексы"
805
+ description: "Индексы с высоким bloat"
806
+ fk_without_indexes:
807
+ name: "FK без индексов"
808
+ description: "Внешние ключи без поддерживающего индекса"
809
+ index_correlation:
810
+ name: "Корреляция индексов"
811
+ description: "Индексы с низкой физической корреляцией"
812
+ index_sizes:
813
+ name: "Размеры индексов"
814
+ description: "Использование диска индексами"
815
+ table_sizes:
816
+ name: "Размеры таблиц"
817
+ description: "Использование диска таблицами"
818
+ bloated_tables:
819
+ name: "Раздутые таблицы"
820
+ description: "Таблицы с высокой долей dead tuples"
821
+ vacuum_needed:
822
+ name: "Нужен VACUUM"
823
+ description: "Таблицы, которым требуется VACUUM"
824
+ row_counts:
825
+ name: "Количество строк"
826
+ description: "Число строк в таблицах"
827
+ cache_hit_ratios:
828
+ name: "Cache hit таблиц"
829
+ description: "Статистика кэша по таблицам"
830
+ seq_scans:
831
+ name: "Sequential scans"
832
+ description: "Таблицы с большим числом sequential scan"
833
+ tables_without_pk:
834
+ name: "Без первичного ключа"
835
+ description: "Таблицы без первичного ключа"
836
+ recently_modified:
837
+ name: "Недавно изменённые"
838
+ description: "Таблицы с недавней активностью"
839
+ update_hotspots:
840
+ name: "Горячие точки UPDATE"
841
+ description: "Одни и те же строки или индексированные колонки часто обновляются"
842
+ unused_tables:
843
+ name: "Неиспользуемые таблицы"
844
+ description: "Таблицы, не запрашиваемые с момента последнего сброса статистики"
845
+ active_connections:
846
+ name: "Активные соединения"
847
+ description: "Текущие подключения к БД"
848
+ connection_stats:
849
+ name: "Статистика соединений"
850
+ description: "Соединения по состоянию"
851
+ long_running_queries:
852
+ name: "Долгие запросы"
853
+ description: "Запросы, выполняющиеся долго"
854
+ blocking_queries:
855
+ name: "Блокирующие запросы"
856
+ description: "Запросы, блокирующие другие"
857
+ locks:
858
+ name: "Блокировки"
859
+ description: "Текущие блокировки в БД"
860
+ idle_connections:
861
+ name: "Idle соединения"
862
+ description: "Простаивающие соединения"
863
+ pool_usage:
864
+ name: "Использование пула"
865
+ description: "Утилизация пула соединений"
866
+ pool_wait_times:
867
+ name: "Время ожидания"
868
+ description: "Анализ ожидания ресурсов"
869
+ pool_saturation:
870
+ name: "Насыщение пула"
871
+ description: "Предупреждения о здоровье пула"
872
+ connection_churn:
873
+ name: "Churn соединений"
874
+ description: "Анализ жизненного цикла соединений"
875
+ database_sizes:
876
+ name: "Размеры баз данных"
877
+ description: "Размер всех баз"
878
+ settings:
879
+ name: "Настройки"
880
+ description: "Конфигурация PostgreSQL"
881
+ extensions:
882
+ name: "Расширения"
883
+ description: "Установленные расширения"
884
+ activity_overview:
885
+ name: "Обзор активности"
886
+ description: "Сводка текущей активности"
887
+ wraparound_risk:
888
+ name: "Риск wraparound"
889
+ description: "Близость к лимиту Transaction ID"
890
+ checkpoint_stats:
891
+ name: "Статистика checkpoint"
892
+ description: "Метрики чекпоинтов и bgwriter"
893
+ cache_stats:
894
+ name: "Статистика кэша"
895
+ description: "Статистика кэширования БД"
896
+ missing_validations:
897
+ name: "Отсутствующие валидации"
898
+ description: "Уникальные индексы без валидаций модели"
899
+ unused_columns:
900
+ name: "Неиспользуемые колонки"
901
+ description: "Колонки, имеющие лишь одно значение"
902
+ always_null_columns:
903
+ name: "Всегда NULL"
904
+ description: "Nullable-колонки, содержащие только NULL"
905
+ polymorphic_without_index:
906
+ name: "Polymorphic без индекса"
907
+ description: "Полиморфные ассоциации без составного индекса"
908
+ counter_cache_issues:
909
+ name: "Проблемы counter_cache"
910
+ description: "counter_cache декларации без целевой колонки"
911
+ soft_delete_without_scope:
912
+ name: "Soft delete без scope"
913
+ description: "Soft-delete колонки без scope, фильтрующего их"
914
+ orphan_tables:
915
+ name: "Orphan-таблицы"
916
+ description: "Таблицы БД без соответствующей Rails-модели"
917
+
918
+ # Filter parameter labels and descriptions
919
+ parameters:
920
+ limit:
921
+ label: "Лимит"
922
+ description: "Максимальное число результатов"
923
+ min_calls:
924
+ label: "Мин. вызовов"
925
+ description: "Минимальное число вызовов запроса"
926
+ min_duration_seconds:
927
+ label: "Мин. длительность (сек)"
928
+ description: "Минимальная длительность запроса в секундах"
929
+ threshold_label: "%{field} — порог"
930
+ threshold_description: "Переопределить порог для %{field}"