복구 실패 - Data Explorer 문제 가능성

정상적으로 작동하는 포럼이 있고, 며칠 전에 문제가 생긴 일부 내용을 확인하려고 합니다. AWS를 사용 중이므로, 정상 작동하는 포럼의 AMI를 생성한 후 새 인스턴스를 시작하고 며칠 전 백업에서 복원을 시도했습니다. 아래 메시지와 함께 실패했습니다.

서버는 정상 작동하는 포럼의 새 이미지에서 구축되었으므로 버전이나 스키마 불일치 문제는 아닐 것입니다.

재구축도 시도해 보았습니다.

하루 전 백업에서 복원도 시도해 보았지만, 결과는 동일했습니다.

이상하게 한 것 중 하나는 업로드 디렉터리(…/uploads/original/1X/*.pdf)에서 PDF 파일을 삭제하여 공간을 확보한 것입니다. 이 단계를 생략하고 다시 시도해 보겠지만, 원인이 될 가능성은 낮아 보입니다.

> [2019-11-30 01:17:44] 'admin' has started the restore!
> [2019-11-30 01:17:44] Marking restore as running...
> [2019-11-30 01:17:44] Making sure /var/www/discourse/tmp/restores/default/2019-11-30-011744 exists...
> [2019-11-30 01:17:44] Downloading archive to tmp directory...
> [2019-11-30 01:23:24] Unzipping archive, this may take a while...
> [2019-11-30 01:27:52] No metadata file to extract.
> [2019-11-30 01:27:52] Validating metadata...
> [2019-11-30 01:27:52]   Current version: 20191129144706
> [2019-11-30 01:27:52]   Restored version: 20191120015344
> [2019-11-30 01:27:52] Extracting dump file...
> [2019-11-30 01:50:57] invalid command \N
> [2019-11-30 01:50:57] invalid command \N
> 
> < repeats about 100 times >
> 
> [2019-11-30 01:51:07] invalid command \N
> [2019-11-30 01:54:13] invalid command \N
> [2019-11-30 01:54:13] EXCEPTION: psql failed
> [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'
> etc...

그럼에도 불구하고, invalid command \N 오류는 Postgres 버전 불일치에서 전형적으로 발생하는 오류입니다…

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

새 서버와 정상 작동 중인 포럼의 버전이 동일합니다.

저는 그 오류들이 PostgreSQL 버전 불일치와 관련이 있다고 생각했지만, 며칠 전 디스크 공간이 부족했던 시스템에서 같은 \N 오류를 확인한 적이 있습니다(백업을 만든 것과 동일한 시스템에 복원 중이었음). 문제를 완전히 진단하지는 못했습니다(당시 또 다른 기이한 문제가 있었고, 다른 서버로 백업을 복원하니 문제가 해결되었습니다. 동일한 서버로 복원했으면 해결되었을지 궁금했었습니다).

공간이 부족하다고 언급하셨는데, 이것이 문제일 것 같습니다. 복원 과정은 백업을 압축 해제하면서 많은 공간을 사용하므로, 압축 해제된 백업 전체 두 부본과 복원에 필요한 공간, 그리고 실패 시 되돌리기 위해 필요한 공간까지 모두 필요합니다.

그러니까 실패하기까지 23분이 걸린다는 거예요?

상황은 더 나빠졌지만, 아마도 근본적인 문제에 더 가까워진 것 같습니다… 더 많은 디스크 공간이 필요하다는 가정 하에, 이전의 50 GB와 달리 이번에는 100 GB 용량으로 이미지를 기반으로 새로운 인스턴스를 생성했습니다. (백업은 각각 5 GB이며 S3에 저장되어 있습니다.) 이번에는 명시적인 오류가 발생했습니다. “No space left on device”(디바이스에 남은 공간 없음)였습니다. 그런데 df 명령을 실행하면 충분한 공간이 남아 있는 것으로 표시됩니다.

> [2019-11-29 22:42:58] Making sure /var/www/discourse/tmp/restores/default/2019-11-29-224258 exists...
> [2019-11-29 22:42:58] Downloading archive to tmp directory...
> [2019-11-29 22:45:46] Unzipping archive, this may take a while...
> [2019-11-29 22:51:46] No metadata file to extract.
> [2019-11-29 22:51:46] Validating metadata...
> [2019-11-29 22:51:46]   Current version: 20191129144706
> [2019-11-29 22:51:46]   Restored version: 20191108000414
> [2019-11-29 22:51:46] Extracting dump file...
> [2019-11-29 22:53:47] EXCEPTION: 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
> Filesystem      Size  Used Avail Use% Mounted on
> /dev/xvda1       99G   28G   71G  28% /

흥미롭게도, 해당 디렉터리에는 아무것도 없습니다:

> /var/www/discourse# ls /shared/tmp/*
> /shared/tmp/backups:
> < empty >
> /shared/tmp/restores:
> < empty >

스왑 파일 문제일까요? EC2 인스턴스는 2 GB 메모리를 가진 t2.small이며, 이미 오래전에 정상 작동하는 포럼에 스왑 파일을 생성해 두었습니다. 새로운 인스턴스에도 스왑 파일이 복제되었을 것으로 추측합니다. 저는 전문가가 아니지만, 스왑 파일이 존재한다고 생각합니다. 왜냐하면 새로운 스왑 파일을 생성하지 못했고, 또한 다음과 같은 이유 때문입니다:

> /var/www/discourse# swapon -s
> Filename                                Type            Size    Used    Priority
> /swapfile                               file            2097148 1024    -2

인오드(inode) 문제일 수 있습니다. df -i 명령어를 실행하면 어떤 결과가 나오나요?

df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
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%를 표시할 텐데).

여전히 해결되지 않았습니다. 작동 중인 EC2 인스턴스의 AMI를 생성하는 대신, 새 Lightsail 인스턴스에서 복원을 시도해 보기로 했습니다. 그래도 실패하지만, 메시지는 약간 다릅니다.

구형과 신형 인스턴스 모두 최신 상태이며, 둘 다 표준 Docker 설치를 사용 중이고, 동일한 postgres 버전을 실행하고 있습니다:

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

다음은 정상적인 현상인가요:

Creating missing functions in the discourse_functions schema
Cannot restore into different schema, restoring in-place

플러그인과 관련이 있을까요? “소스” 사이트에는 지원되는 플러그인과 사용자 정의 플러그인 등 여러 플러그인이 설치되어 있습니다. 일부는 사용자 정의 사용자 필드를 사용합니다. 플러그인이 있는 경우와 없는 경우 모두를 포함하여, 플러그인이 없는 “대상” 사이트로 복원을 시도해 보았습니다.

스키마 비교를 시작하는 방법에 대한 조언을 구합니다.

> [2019-12-07 04:51:36] 'admin' has started the restore!
> [2019-12-07 04:51:36] Marking restore as running...
> [2019-12-07 04:51:36] Making sure /var/www/discourse/tmp/restores/default/2019-12-07-045136 exists...
> [2019-12-07 04:51:36] Downloading archive to tmp directory...
> [2019-12-07 04:53:49] Unzipping archive, this may take a while...
> [2019-12-07 04:57:12] No metadata file to extract.
> [2019-12-07 04:57:12] Validating metadata...
> [2019-12-07 04:57:12]   Current version: 20191129144706
> [2019-12-07 04:57:12]   Restored version: 20191120015344
> [2019-12-07 04:57:12] Extracting dump file...
> [2019-12-07 04:59:10] Creating missing functions in the discourse_functions schema
> [2019-12-07 04:59:11] Cannot restore into different schema, restoring in-place
> [2019-12-07 05:05:02] ERROR:  current transaction is aborted, commands ignored until end of transaction block
> [2019-12-07 05:05:03] ERROR:  current transaction is aborted, commands ignored until end of transaction block
> < repeats about 100 times >
> [2019-12-07 05:05:03] ERROR:  current transaction is aborted, commands ignored until end of transaction block
> [2019-12-07 05:05:03] EXCEPTION: psql failed
> [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'
> < rest of traceback >

네, 정상적인 현상입니다.

PostgreSQL 내부에서 분명 문제가 발생하고 있습니다. 로그를 확인해 보셨나요?

가능성이 낮긴 하지만, 메모리 관련 문제일 수도 있습니다. 복원하는 동안 free -m 출력을 모니터링해 보시고, (가상) 메모리가 부족해지는지 확인해 보시겠습니까?

문제가 무엇인지 정확히 모르기 때문에 어려운 질문이라는 것은 알지만, 일반적으로 복원을 시도하기 전에 대상 사이트에 플러그인을 설치해야 하나요? 아니면 복원 과정에서 플러그인을 가져와서 구축하는 건가요?

네, 설치해야 합니다. 복원 과정에서는 자동으로 설치되지 않습니다.

다만, 이 문제가 원인이 아닐 것으로 보입니다. 복원 시에는 올바른 데이터베이스 구조(플러그인 관련 항목 포함)가 자동으로 처리되므로요.

메모리 4GB인 $20짜리 새 Lightsail 인스턴스를 만들었습니다. 복원 중에 'free -m’을 관찰했는데, 항상 충분한 여유 및 사용 가능 메모리가 있었습니다.

구체적인 오류는 플러그인이 설치되어 있느냐에 따라 다르지만, 아마도 동일한 근본 원인으로 인해 발생하는 것 같습니다. 이 경우 백업 전에 플러그인을 설치하지 않았습니다. 오류는 두 가지 유형으로 나뉩니다:

  1. postgres 로그에서 이러한 오류가 많이 발생하며, 때로는 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회 반복 후 종료 >

그리고 postgres 로그의 해당하는 출력:

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번은 치명적이지 않고, 제자리 복구(in-place restore)의 부수적 결과일 뿐이라고 생각합니다.

백업을 생성하기 전에 데이터 탐색기(Data Explorer) 쿼리를 모두 (내보내고) 삭제하고, 데이터 탐색기 플러그인을 제거하는 것을 고려해 볼 수 있습니다.

또는 plugin_store_rows 테이블의 관련 내용을 게시해 주실 수 있나요?

실제로 (plugin_name, key) 쌍이 중복된 중복 쿼리가 존재하며, 예를 들어 q:-11과 q:-2가 해당됩니다. 하지만 id는 고유합니다. 중복 항목 사이에 특정 패턴이 보이지 않습니다. 예를 들어, 제가 선호하는 쿼리가 아니거나 하는 식의 규칙은 없습니다.

따라서 다음 단계로 중복 항목을 제거하고, 백업을 만든 뒤 해당 백업에서 복원해 보려고 합니다.

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 파일에서 중복된 줄을 삭제하면 될 것입니다.

개발 데이터베이스에 해당 인덱스가 어떤 이유로 누락되어 있을 수도 있습니다?