Восстановление не удаётся: возможна проблема с Data Explorer

У меня есть рабочий форум, и я хочу восстановить состояние, которое было несколько дней назад, когда всё работало корректно. Я работаю на AWS, поэтому создал AMI-образ рабочего форума, запустил новый экземпляр и попытался восстановить резервную копию от нескольких дней назад. Это не удалось, и я получил сообщения об ошибках, приведённые ниже.

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

Я пробовал пересоздать окружение.

Я пробовал восстановить из другой резервной копии от одного дня назад — результат тот же.

Единственное необычное действие, которое я предпринял, — это удаление файлов PDF из каталога загрузки (…/uploads/original/1X/*.pdf) для освобождения места. Я попробую снова, пропустив этот шаг, но маловероятно, что это стало причиной проблемы.

> [2019-11-30 01:17:44] 'admin' начал восстановление!
> [2019-11-30 01:17:44] Пометка восстановления как выполняющегося...
> [2019-11-30 01:17:44] Проверка существования /var/www/discourse/tmp/restores/default/2019-11-30-011744...
> [2019-11-30 01:17:44] Загрузка архива во временный каталог...
> [2019-11-30 01:23:24] Распаковка архива, это может занять время...
> [2019-11-30 01:27:52] Файл метаданных для извлечения отсутствует.
> [2019-11-30 01:27:52] Проверка метаданных...
> [2019-11-30 01:27:52]   Текущая версия: 20191129144706
> [2019-11-30 01:27:52]   Версия для восстановления: 20191120015344
> [2019-11-30 01:27:52] Извлечение файла дампа...
> [2019-11-30 01:50:57] недопустимая команда \N
> [2019-11-30 01:50:57] недопустимая команда \N
> 
> < повторяется около 100 раз >
> 
> [2019-11-30 01:51:07] недопустимая команда \N
> [2019-11-30 01:54:13] недопустимая команда \N
> [2019-11-30 01:54:13] ИСКЛЮЧЕНИЕ: psql завершился с ошибкой
> [2019-11-30 01:54:14] /var/www/discourse/lib/backup_restore/restorer.rb:331:in `restore_dump'
> /var/www/discourse/lib/backup_restore/restorer.rb:75:in `run'
> /var/www/discourse/lib/backup_restore.rb:166:in `block in start!'
> /var/www/discourse/lib/backup_restore.rb:163:in `fork'
> /var/www/discourse/lib/backup_restore.rb:163:in `start!'
> /var/www/discourse/lib/backup_restore.rb:22:in `restore!'
> /var/www/discourse/app/controllers/admin/backups_controller.rb:119:in `restore'
> и т. д...

Тем не менее, ошибка invalid command \N типична для несоответствия версий Postgres…

root@example:/var/www/discourse# psql --version
psql (PostgreSQL) 10.10 (Debian 10.10-1.pgdg100+1)

На новом сервере установлена та же версия, и форум работает.

Я склонен связывать эти ошибки с несовпадением версий PostgreSQL, но я видел подобные ошибки с \N на днях на системе, у которой закончилось место на диске (я восстанавливал данные на ту же систему, с которой была сделана резервная копия). Я не закончил диагностику проблемы (это была ещё одна странная проблема, с которой я столкнулся, и восстановление резервной копии на другой сервер решило её; я задумался, решило бы восстановление на тот же сервер проблему).

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

Получается, что он работает 23 минуты, а затем выдаёт ошибку?

Становится ещё хуже, но, возможно, это ближе к истинной проблеме… Исходя из гипотезы, что мне нужно больше места на диске, я создал новый экземпляр из своего образа, на этот раз с 100 ГБ вместо предыдущих 50 ГБ. (Резервные копии занимают по 5 ГБ каждая и хранятся в S3.) На этот раз я получил явную ошибку: «No space left on device» (Место на устройстве закончилось). Однако команда df показывает огромное количество свободного места.

> [2019-11-29 22:42:58] Убедимся, что каталог /var/www/discourse/tmp/restores/default/2019-11-29-224258 существует...
> [2019-11-29 22:42:58] Загрузка архива во временный каталог...
> [2019-11-29 22:45:46] Распаковка архива, это может занять некоторое время...
> [2019-11-29 22:51:46] Файл метаданных для извлечения отсутствует.
> [2019-11-29 22:51:46] Проверка метаданных...
> [2019-11-29 22:51:46]   Текущая версия: 20191129144706
> [2019-11-29 22:51:46]   Восстанавливаемая версия: 20191108000414
> [2019-11-29 22:51:46] Извлечение файла дампа...
> [2019-11-29 22:53:47] ИСКЛЮЧЕНИЕ: No space left on device @ io_write - /shared/tmp/restores/default/2019-11-29-224258/dump.sql

> [ec2-user@ip-172-31-47-237 discourse]$ df / -h
> Файловая система      Размер  Использовано  Доступно  Использ.  Точка монтирования
> /dev/xvda1            99G     28G           71G       28%       /

Интересно, что в указанном каталоге ничего нет:

> /var/www/discourse# ls /shared/tmp/*
> /shared/tmp/backups:
> < пусто >
> /shared/tmp/restores:
> < пусто >

Неужели это проблема с файлом подкачки? Экземпляр EC2 имеет тип t2.small с 2 ГБ оперативной памяти, поэтому я давно создал файл подкачки на рабочем форуме. Предполагаю, что файл подкачки был скопирован на новый экземпляр. Я не эксперт, но считаю, что файл подкачки существует, так как система не позволила мне создать новый, и потому что:

> /var/www/discourse# swapon -s
> Имя файла                               Тип             Размер    Использовано  Приоритет
> /swapfile                               файл            2097148   1024          -2

Это может быть проблема с inode’ами. Что показывает команда df -i?

df -i
Файловая система Inodes IUsed IFree IUse% Монтирована на
devtmpfs 252562 437 252125 1% /dev
tmpfs 255203 1 255202 1% /dev/shm
/dev/xvda1 6553600 737194 5816406 12% /

Выхожу за рамки своей компетенции, но полагаю, что это не плохо?

Нет, всё в порядке.

Ошибка «No space left on device» возникает не только тогда, когда на устройстве заканчиваются гигабайты, но и когда файловая система исчерпывает inode. Однако в данном случае это явно не проблема (иначе iUse% был бы равен 100%).

Всё ещё безрезультатно. Я подумал, что попробую восстановить на новом экземпляре Lightsail, вместо запуска AMI моего рабочего экземпляра EC2. Ошибка всё ещё возникает, но сообщения немного отличаются.

Оба экземпляра, старый и новый, обновлены, оба установлены как стандартные Docker-инсталляции, и оба используют одну и ту же версию PostgreSQL:

psql --version
psql (PostgreSQL) 10.10 (Debian 10.10-1.pgdg100+1)

Нормально ли это:

Создание отсутствующих функций в схеме discourse_functions
Невозможно восстановить в другую схему, восстановление выполняется на месте

Может ли это быть связано с плагинами? На «исходном» сайте установлено несколько плагинов, как поддерживаемых, так и кастомных. Некоторые из них используют пользовательские поля. Я пробовал восстанавливать на чистые «целевые» сайты как с плагинами, так и без них.

Есть ли какие-либо рекомендации, как начать сравнение схем?

> [2019-12-07 04:51:36] 'admin' начал восстановление!
> [2019-12-07 04:51:36] Пометка восстановления как запущенного...
> [2019-12-07 04:51:36] Проверка существования /var/www/discourse/tmp/restores/default/2019-12-07-045136...
> [2019-12-07 04:51:36] Загрузка архива во временную директорию...
> [2019-12-07 04:53:49] Распаковка архива, это может занять время...
> [2019-12-07 04:57:12] Файл метаданных для извлечения отсутствует.
> [2019-12-07 04:57:12] Проверка метаданных...
> [2019-12-07 04:57:12]   Текущая версия: 20191129144706
> [2019-12-07 04:57:12]   Восстанавливаемая версия: 20191120015344
> [2019-12-07 04:57:12] Извлечение файла дампа...
> [2019-12-07 04:59:10] Создание отсутствующих функций в схеме discourse_functions
> [2019-12-07 04:59:11] Невозможно восстановить в другую схему, восстановление выполняется на месте
> [2019-12-07 05:05:02] ОШИБКА: текущая транзакция прервана, команды игнорируются до конца блока транзакции
> [2019-12-07 05:05:03] ОШИБКА: текущая транзакция прервана, команды игнорируются до конца блока транзакции
> < повторяется около 100 раз >
> [2019-12-07 05:05:03] ОШИБКА: текущая транзакция прервана, команды игнорируются до конца блока транзакции
> [2019-12-07 05:05:03] ИСКЛЮЧЕНИЕ: psql не удался
> [2019-12-07 05:05:03] /var/www/discourse/lib/backup_restore/restorer.rb:331:in `restore_dump'
> /var/www/discourse/lib/backup_restore/restorer.rb:75:in `run'
> /var/www/discourse/lib/backup_restore.rb:166:in `block in start!'
> /var/www/discourse/lib/backup_restore.rb:163:in `fork'
> /var/www/discourse/lib/backup_restore.rb:163:in `start!'
> /var/www/discourse/lib/backup_restore.rb:22:in `restore!'
> /var/www/discourse/app/controllers/admin/backups_controller.rb:119:in `restore'
> < остальная часть трассировки стека >

Да, это нормально.

Внутри PostgreSQL явно что-то идёт не так. Вы смотрели её логи?

Маловероятно, но возможно: это может быть связано с памятью? Попробуйте отслеживать вывод команды free -m во время восстановления и посмотрите, не закончится ли у вас (виртуальная) память.

Я понимаю, что это сложный вопрос, так как мы не знаем, в чём именно проблема, но в целом: нужно ли устанавливать плагины на целевом сайте перед попыткой восстановления? Или же процесс восстановления сам подтягивает и собирает плагины?

Да, это необходимо — процесс восстановления этого за вас не сделает.

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

Я создал новый экземпляр Lightsail за $20 с 4 ГБ памяти. Я наблюдал за выводом free -m во время восстановления. Памяти всегда было достаточно свободной и доступной.

Конкретные ошибки варьируются в зависимости от того, установлены ли плагины, но, вероятно, они вызваны одной и той же корневой причиной. В данном случае я не устанавливал плагины перед созданием резервной копии. Существует два класса ошибок:

  1. В логах PostgreSQL я получаю много таких сообщений, иногда на символе 34, а иногда на символе 41.

discourse@discourse ERROR: relation “user_auth_tokens” does not exist at character 34

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

  1. Вторая ошибка зависит от того, установлены ли плагины. В данном случае я их не устанавливал, поэтому получаю ошибку, связанную с Data Explorer, и именно в этот момент всё идёт наперекосяк.

В логах администратора > резервное копирование > лог:

[2019-12-07 07:38:34] CREATE INDEX
[2019-12-07 07:38:34] CREATE INDEX
[2019-12-07 07:38:34] ERROR: could not create unique index “index_plugin_store_rows_on_plugin_name_and_key”
[2019-12-07 07:38:34] DETAIL: Key (plugin_name, key)=(discourse-data-explorer, q:-6) is duplicated.
[2019-12-07 07:38:34] ERROR: current transaction is aborted, commands ignored until end of transaction block
[2019-12-07 07:38:34] ERROR: current transaction is aborted, commands ignored until end of transaction block
< повторяется 1000 раз и завершается >

А соответствующий вывод в логе PostgreSQL:

2019-12-07 07:38:34.718 UTC [8991] discourse@discourse LOG: duration: 165.427 ms statement: CREATE INDEX index_notifications_on_user_
id_and_topic_id_and_post_number ON public.notifications USING btree (user_id, topic_id, post_number);
2019-12-07 07:38:34.767 UTC [8991] discourse@discourse ERROR: could not create unique index “index_plugin_store_rows_on_plugin_name_an
d_key”
2019-12-07 07:38:34.767 UTC [8991] discourse@discourse DETAIL: Key (plugin_name, key)=(discourse-data-explorer, q:-6) is duplicated.
2019-12-07 07:38:34.767 UTC [8991] discourse@discourse STATEMENT: CREATE UNIQUE INDEX index_plugin_store_rows_on_plugin_name_and_key O
N public.plugin_store_rows USING btree (plugin_name, key);
2019-12-07 07:38:34.984 UTC [8991] discourse@discourse ERROR: current transaction is aborted, commands ignored until end of transactio
n block
2019-12-07 07:38:34.984 UTC [8991] discourse@discourse STATEMENT: CREATE INDEX index_policy_users_on_post_policy_id_and_user_id ON pub
lic.policy_users USING btree (post_policy_id, user_id);

Я думаю, что проблема #1 не является критической и является лишь побочным эффектом восстановления в том же месте.

Вы можете рассмотреть возможность (экспорта и) удаления всех запросов из Data Explorer и отключения плагина Data Explorer перед созданием резервной копии.

Или, возможно, вы могли бы опубликовать соответствующее содержимое таблицы plugin_store_rows?

Действительно, есть дублирующиеся запросы с одинаковыми парами (plugin_name, key), например q:-11 и q:-2, но с уникальными идентификаторами. Я не вижу никакой закономерности среди дубликатов — например, это не мои любимые запросы или что-то подобное.

Поэтому следующим шагом будет удаление дубликатов, создание резервной копии и попытка восстановления из неё.

SELECT id, plugin_name, key from plugin_store_rows
WHERE plugin_name = ‘discourse-data-explorer’
ORDER BY key

id plugin_name key
1138 discourse-data-explorer q:-1
1136 discourse-data-explorer q:-10
813 discourse-data-explorer q:10
1142 discourse-data-explorer q:-11
1397 discourse-data-explorer q:-11
825 discourse-data-explorer q:11
889 discourse-data-explorer q:13
1004 discourse-data-explorer q:14
1005 discourse-data-explorer q:15
1043 discourse-data-explorer q:17
1044 discourse-data-explorer q:18
514 discourse-data-explorer q:-2
1249 discourse-data-explorer q:-2
764 discourse-data-explorer q:2
1053 discourse-data-explorer q:21
1066 discourse-data-explorer q:22
1082 discourse-data-explorer q:23
1097 discourse-data-explorer q:24
1131 discourse-data-explorer q:26
1132 discourse-data-explorer q:27
1134 discourse-data-explorer q:28
1135 discourse-data-explorer q:29
775 discourse-data-explorer q:3
1137 discourse-data-explorer q:30
1140 discourse-data-explorer q:31
1141 discourse-data-explorer q:32
1143 discourse-data-explorer q:33
1149 discourse-data-explorer q:34
1155 discourse-data-explorer q:35
1156 discourse-data-explorer q:36
1157 discourse-data-explorer q:37
1158 discourse-data-explorer q:38
1161 discourse-data-explorer q:39
513 discourse-data-explorer q:-4
777 discourse-data-explorer q:4
1211 discourse-data-explorer q:40
1215 discourse-data-explorer q:41
1223 discourse-data-explorer q:42
1224 discourse-data-explorer q:43
1225 discourse-data-explorer q:44
1226 discourse-data-explorer q:45
1269 discourse-data-explorer q:46
1272 discourse-data-explorer q:47
1273 discourse-data-explorer q:48
1274 discourse-data-explorer q:49
1279 discourse-data-explorer q:50
1281 discourse-data-explorer q:51
1282 discourse-data-explorer q:52
1301 discourse-data-explorer q:53
1349 discourse-data-explorer q:54
1369 discourse-data-explorer q:55
1373 discourse-data-explorer q:56
1384 discourse-data-explorer q:57
1387 discourse-data-explorer q:58
1396 discourse-data-explorer q:59
1222 discourse-data-explorer q:-6
1348 discourse-data-explorer q:-6
781 discourse-data-explorer q:6
763 discourse-data-explorer q:-7
782 discourse-data-explorer q:7
515 discourse-data-explorer q:-8
791 discourse-data-explorer q:8
1139 discourse-data-explorer q:-9
798 discourse-data-explorer q:9
507 discourse-data-explorer q:_id

На самом деле, как можно удалить дубликаты? Все три принадлежат “system”, поэтому я не могу их редактировать или удалять.

Нашёл закономерность. Когда я запускаю запрос, принадлежащий системе, создаётся дубликат, что, очевидно, ломает восстановление.

Я не могу воспроизвести это на чистом тестовом сайте, но на моём продакшн-сайте проблема возникает стабильно. Я установил все плагины с продакшна на тестовый сайт, но всё равно не могу воспроизвести ошибку.

  1. Как мне найти, что не так с моим продакшн-сайтом?

  2. Как удалить дублирующиеся запросы, если они принадлежат системе? Мне нужно выполнить sudo -u postgres psql discourse...? Звучит страшно.

Чтобы резервную копию можно было восстановить, вы, вероятно, можете удалить дублирующиеся строки из файла резервной копии SQL.

Возможно, по какой-то причине индекс отсутствует в базе данных разработчика?