Недавно я открыл код еще одного инструмента, который уже около года использую на работе - db-checker.

Началось все с необходимости проводить регулярные проверки логической целостности данных в БД. Проще говоря - гонять мониторингом запросы к базе.

Сначала это была часть проекта, который проверял данные на нашем CDN. Проект этот сразу планировался многопоточным, поэтому написан на Go. Затем мухи были отделены от котлет, и проверка базы выделилась в отдельную сущность, но несколько legacy-моментов осталось.

Главной проблемой было то, что проверки определялись в коде, поэтому добавление новой проверки было относительно нетривиальной задачей, а код проекта невозможно было сделать открытым. Долгое время у меня не было времени на устранение этого недостатка, но недавно я взялся за эту задачу.

db-checker

Результатом стал проект db-checker, который работает как Nagios-совместимый плагин, и позволяет:

  • читать описание проверок из простых YAML-файлов
  • делать запросы к PostgreSQL/MySQL и проверять наличие/отсутствие результата
  • выводить полученные данные в удобной табличной форме
  • сохранять состояние прошлой проверки и выводить алерты только для новых проблем

Пройдемся по каждому пункту отдельно.

Читать описание проверок из простых YAML-файлов

Я очень люблю YAML как формат для конфигов, и описание проверок в нем выглядят крайне лаконично и просто (примеры в следующем пункте). При этом хранение проверок отдельно от инструмента очень удобно и дает возможность очень легко добавлять новые проверки любому человеку.

Все описание проверки состоит всего из 3х обязательных полей:

  • query: любой SQL-запрос
  • description: Описание проверки
  • assert: что мы ожидаем от результата выполнения запроса, present или absent

Делать запросы к PostgreSQL/MySQL и проверять наличие/отсутствие результата

Все проверки целостности данных в базе можно выразить в форме SQL-запроса, который либо возвращает ОК в случае пройденной проверки, и тогда мы проверяем наличие данных в ответе на запрос, либо мы запрашиваем список того, чего быть не должно, и тогда мы проверяем отсутствие данных в ответе на запрос, а при наличии - выводим их в отчете.

Например, наличие блокировок в PostgreSQL можно проверять так:

query: |
    SELECT
      COALESCE(blockingl.relation::regclass::text,blockingl.locktype) as locked_item,
      (now() - blockeda.query_start)::time AS waiting_duration,
      blockeda.pid AS blocked_pid,
      blockeda.query as blocked_query, blockedl.mode as blocked_mode,
      blockinga.pid AS blocking_pid, blockinga.query as blocking_query,
      blockingl.mode as blocking_mode
    FROM pg_catalog.pg_locks blockedl
    JOIN pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
    JOIN pg_catalog.pg_locks blockingl ON(
      ( (blockingl.transactionid=blockedl.transactionid) OR
      (blockingl.relation=blockedl.relation AND blockingl.locktype=blockedl.locktype)
      ) AND blockedl.pid != blockingl.pid)
    JOIN pg_stat_activity blockinga ON blockingl.pid = blockinga.pid
      AND blockinga.datid = blockeda.datid
    WHERE NOT blockedl.granted;
description: Locks in database
assert: absent

Таким образом, если блокировок нет - все отлично, а есть есть - выводим их список.

Другой пример - проверка успешных задач job_name PGAgent за 4 часа:

query: |
    SELECT jlgstatus
    FROM pgagent.pga_joblog
    WHERE jlgstart > (now() - interval '4 hours') AND jlgstatus = 's' AND
    jlgjobid=(
    SELECT jobid FROM pgagent.pga_job WHERE jobname = 'job_name'
    )
    ORDER BY jlgstart DESC LIMIT 1;
description: Не было успешных запусков job_name за прошедший час
assert: present

В этом случае мы ждем, что Query вернет нам список задач, а если нет - алерт.

Выводить полученные данные в удобной табличной форме

Поскольку сообщения из алертов читают люди, надо показывать проблему так, чтобы сразу было все понятно, поэтому ASCII-таблица с проблемными значениями подходит как нельзя лучше.

Пример:

nagios@example.com:~$ ./db-checker --dbname movies --dbuser=checker --dbhost=localhost --dbpassword=SomePassword --checks /opt/checks/movies --critical
CRITICAL:
* Found movies with zero duration
N. ¦ column1 ¦ orig_title                ¦ rus_title
1. ¦ 1346    ¦ Midnight Express          ¦ Полуночный экспресс
2. ¦ 2165    ¦ In the Loop               ¦ В петле
3. ¦ 2254    ¦ Sex & Drugs & Rock & Roll ¦ Секс, наркотики и рок-н-ролл
4. ¦ 2534    ¦ Resident Evil: Damnation  ¦ Обитель Зла: Проклятие

Сохранять состояние прошлой проверки и выводить алерты только для новых проблем

Предположим, у нас есть некая таблица биллинга, в которой содержатся записи о транзакциях пользователей. Иногда случаются подозрительные повторные платежи, о каждом из которых стоит слать уведомление, но лишь единожды. При этом транзакции из таблицы никуда не пропадают. Вот как раз для такого случая есть --diff режим, который позволяет уведомлять только о новых проблемах.

Прочие особенности

Поскольку проект написан на Go, грех было не реализовать параллельное выполнение проверок (настраивается с помощью флага --concurrent-checks).

И конечно я постарался весь важный код покрыть тестами.

Альтернативы

До принятия решения о написании своего велосипеда я изучал уже готовые утилиты. Самое близкое по функционалу, что я нашел - check_postgres.pl, монструозный скрипт на Perl с более чем 8k строк кода, зато с возможностью запускать ограниченное подмножество запросов через custom_query. Под мои задачи он не подошел, но штука впечатляющая.

Заключение

Больше информации и примеры проверок можно найти в репозитории проекта, код опубликован под лицензией MIT.


Comments

comments powered by Disqus