[Ghost] 우당탕탕 버전 업데이트 후기

블로그의 업타임 그래프. 값이 없는 부분(혹은 음영으로 칠해진 부분) 동안 블로그 접속이 불가능했다
블로그의 업타임 그래프. 값이 없는 부분(혹은 음영으로 칠해진 부분) 동안 블로그 접속이 불가능했다

하필 새벽에 갑자기 하고 싶었던 업데이트

업데이트만큼 설레는 데이트는 없다(응?). 오늘 사고가 발생하기 전까지 내 Ghost 블로그는 5.26.4 버전의 Docker 이미지를 사용하고 있었다. 글 작성 시점 기준으로 최신 메이저 버전을 사용하고 있었기 때문에, 굳이 지금 당장 업데이트할 필요는 없었다. 아래 공지를 보기 전까지는..

이모티콘 자동완성? 이건 못 참는데.. 😋

슬랙이나 노션처럼 이모티콘 자동완성이 된다니, 어찌 업데이트를 안할 수 있겠는가?

바로 Ghost가 배포된 서버로 들어가 docker-compose 파일의 Ghost 이미지 버전을 최신 버전인 5.75.1로 수정했고, 컨테이너를 재시작했다. Ghost는 knex-migrator라는 자체 데이터베이스 마이그레이션 도구를 사용하고 있는데, 여느 마이그레이션 프레임워크들과 마찬가지로 Ghost 버전이 업데이트될 때 자동으로 필요한 마이그레이션 스크립트들을 차례로 실행해준다. 그래, 마이너 버전 업데이트니 괜찮겠지라며 데이터베이스 백업은 진행하지 않았다.

내가 큰 실수를 했다는 걸 깨달은 건 악당을 순조롭게 해치운 영웅처럼 행복한 표정을 짓고 다시 블로그에 들어갔을 때였다.

Ghost 블로그에서 데이터베이스에 제대로 연결할 수 없을 때 나오는 화면. 당시엔 너무 당황해서 스크린샷을 찍지 못했고, Ghost 포럼에서 대신 가져왔다. 출처: https://forum.ghost.org/t/update-stuck-wel-be-right-back/33536
Ghost 블로그에서 데이터베이스에 제대로 연결할 수 없을 때 나오는 화면. 당시엔 너무 당황해서 스크린샷을 남기지 못했고, Ghost 포럼의 다른 사용자 스크린샷을 대신 가져왔다. 이미지 출처: https://forum.ghost.org/t/update-stuck-wel-be-right-back/33536
"별것도 아닌 일에 어 금지" - IT 회사들 벽에 종종 붙어 있는 문구

"어?"

"MySQL 5.7은 지원하지 않습니다"

메인 페이지도, 관리자 페이지도 모두 접속이 불가능했다. 우리의 Ghost 컨테이너는 오류를 뿜어내며 계속 재시작되고 있었다(재시작 옵션을 "unless-stopped"로 해놓았기 때문이다). 본능적으로 Ghost 서버 프로세스의 로그부터 확인했다. 데이터베이스 마이그레이션이 실행되다가 중간에 현재 데이터베이스가 지원하지 않는 문법의 SQL문이 실행돼 마이그레이션이 멈춘 것 같았다.

내 블로그가 띄워진 가상머신은 매일 새벽 4시에 전체 볼륨이 백업되고 있다. 만약 데이터베이스가 복구 불가능하다면, 어제 새벽 4시 상태로 롤백해야 할 것이다. 일단 knex-migrator 도구를 사용해 기존에 실행되던 마이그레이션들을 롤백하고, Ghost 버전을 5.26.4로 낮추었다. 다행히 다시 접속이 된다. 블로그 글들도 멀쩡하다. 데이터베이스 롤백은 필요없을 것 같았다.

내가 Ghost 버전 업데이트를 잘못 했나 싶어 Ghost 문서의 업데이트 방법을 다시 한 번 읽어보았다.

MySQL 8 is the only supported database in production(프로덕션 환경에서는 MySQL 8버전 데이터베이스만 지원됩니다). - Ghost 업데이트 방법 문서(https://ghost.org/docs/update-major-version/)

다시 서버로 들어가 MySQL 컨테이너 버전을 확인해봤다. 아뿔싸! 5.7이다.. MySQL 5.7은 Ghost 4 버전에서만 정식으로 지원하고 있었는데, 나는 그동안 아무 문제가 없었으니 데이터베이스를 업데이트하지 않고 계속 써왔던 것이다. 그나마 다행인 것은, MySQL 5.7을 사용하다 곤혹을 겪은 게 나 혼자가 아니었다는 것이다. 어찌나 많았는지 Ghost 공식 문서에서도 MySQL 버전을 업데이트했을 때 발생할 수 있는 문자열 인코딩 문제와 그 해결법을 기록해두고 있었다. 이번에는 데이터베이스를 철저히 백업하고 아래 스크립트들을 실행했다.

MySQL 버전 업그레이드 후 실행해야 하는 커맨드들을 안내하는 문서. 이미지 출처: https://ghost.org/docs/faq/supported-databases
문서대로만 따라하면 모든 column들의 collation(문자열 데이터들을 비교하기 위한 문자들의 순서 기준)을 쉽게 변경할 수 있다. 커맨드만 봤을 뿐인데 이걸 밝혀내기 위해 Ghost 사용자들이 겪었을 고생길이 훤하게 보이는 건 기분 탓이었을까? 이미지 출처: https://ghost.org/docs/faq/supported-databases

계속되는 문제와 수동 마이그레이션

이제 MySQL 컨테이너 버전도 최신 버전인 8.2.0이 되었다. 그런데 Ghost 버전을 올리니 이번에도 데이터베이스 마이그레이션이 실패해 Ghost 블로그가 시작되지 않았다.

[2023-12-08 19:01:33] ERROR update `members` inner join `suppressions` on `members`.`email` = `suppressions`.`email` set `email_disabled` = true - Unknown column 'suppressions.email' in 'on clause'

update `members` inner join `suppressions` on `members`.`email` = `suppressions`.`email` set `email_disabled` = true - Unknown column 'suppressions.email' in 'on clause'

{"config":{"transaction":true},"name":"2023-07-15-10-11-12-update-members-email-disabled-field.js"}
"Error occurred while executing the following migration: 2023-07-15-10-11-12-update-members-email-disabled-field.js"

Error ID:
    300

Error Code:
    ER_BAD_FIELD_ERROR

당시 Ghost 컨테이너의 에러 로그. suppressions 테이블에는 email column이 없다고 했다.

Ghost 버전 문제인가 싶어 5.26.4 버전과 5.75.1 버전 사이의 이런저런 버전들을 계속 시도해봤다(그러면서 블로그는 계속 접속 가능 상태와 불가능 상태를 오갔다). 그러다가 5.57.2 버전에서 실행되는 마이그레이션 스크립트에서 문제가 발생했음을 확인했다. 5.57.2 버전의 문제인가 싶어 구글에 검색해봐도 나와 비슷한 사례가 하나도 없었다. 그렇다면 아마도 저 버전 Ghost의 문제는 아니었다.

정말 suppressions 테이블에는 email column이 없었을까? MySQL 셸을 실행해 suppressions 테이블을 조회해봤다. 결과는 조금 놀라웠다.

mysql> SHOW COLUMNS from suppressions;
+----------------+--------------+------+-----+---------+-------+
| Field          | Type         | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| id             | varchar(24)  | NO   | PRI | NULL    |       |
| email_address  | varchar(191) | YES  | UNI | NULL    |       |
| email_id       | varchar(24)  | YES  | MUL | NULL    |       |
| reason         | varchar(50)  | NO   |     | NULL    |       |
| created_at     | datetime     | NO   |     | NULL    |       |
+----------------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

그러니까 저 테이블에는 email이 아니라 email_address가 들어가 있었다. 왜냐구? GitHub에서 Ghost의 마이그레이션 스크립트들을 찾아보니 5.27 버전에서 email_address 필드를 email로 변경하는 마이그레이션이 진행됐다고 한다(오픈소스라 이렇게 바로 볼 수 있는 점은 참 좋았다). 앞서 마이그레이션을 롤백한 적이 있었는데, 다시 실행하는 과정에서 이 마이그레이션 스크립트의 실행이 누락된 것 같았다. 해당 버전의 앞뒤 마이너 버전들의 마이그레이션 스크립트들도 살펴보았는데, 현재 데이터베이스와 대조해보니 다른 마이그레이션 스크립트들은 잘 실행된 것 같았다. 이 마이그레이션만 잘 실행하면 모든 게 제자리로 돌아올 것이다. Ghost 서버도, 내 블로그 글들도, 초조했던 내 마음도.

어느덧 시간은 새벽 4시. 이 시간에도 답변을 해줄 수 있는 유일한 친구인 ChatGPT에게(매달 친구비를 내고 있다) 한 번 더 확인을 받고 ALTER TABLE 문을 실행했다.

mysql> ALTER TABLE suppressions CHANGE email_address email VARCHAR(191);

고마워 ChatGPT

그런 다음 Ghost 서버를 다시 업데이트했다. 지금 사용중인 Alto 테마의 버전이 Ghost 버전보다 낮다는 경고가 발생한다. 다른 오류는 발생하지 않았다. 바로 블로그에 접속해 글들이 멀쩡한지 확인한다. Ghost 서버도, 내 블로그 글들도 있어야 할 곳에 돌아와 있었다. 내 마음도 한 시간 반여 만에 안정을 되찾았다. 마지막으로 Alto 테마의 버전을 업데이트하면서 "태풍을 부르는" Ghost 버전 업데이트도 끝이 났다.

결론

이모지 입력 화면 😆

그렇게 되어 현재 내 블로그는 작성일 기준 최신 버전으로 잘 실행되고 있다. 이모지도 보시다시피 잘 입력할 수 있다 🤔

사실 MySQL 버전을 진작에 업데이트 했었더라면 발생하지 않았어야 할 문제였다. 이전 버전의 Ghost 문서만 보고 Ghost를 배포해버린 나의 모습을 반성하면서, 아래와 같은 교훈들도 얻었다.

  • 소프트웨어 버전을 올릴 때에는 항상 의존하는 소프트웨어(여기서는 MySQL)의 업데이트가 먼저 필요하지는 않은지 확인해보자.
  • 정기 백업과 상관없이, 마이그레이션이 수반되는 업데이트에서는 데이터베이스 백업을 반드시 진행하자. 반드시!
  • 다음 번에는 다운타임이 발생할 때 방문자들이 당황하지 않게 Uptime Kuma(웹서비스 상태 체크 도구)에서 제공하는 점검 페이지를 띄워봐야겠다.
  • 서버 관리할 시간이 아깝다면 돈을 써서 관리형 서비스인 Ghost Pro를 사용하자.. 🥲

이렇게 잘 기록해두었으니 다음에는 이런 일이 없을 것이다. 아마도.

요약 (by Bard) 🎅

산타 할아버지: 옛날 옛적에, 한 아이가 있었어. 그 아이는 블로그를 운영했는데, 어느 날 새벽에 귀여운 이모티콘을 사용할 수 있는 최신 버전으로 업데이트하기로 했어.

아이: 귀엽다!

산타 할아버지: 아이는 새벽에 일어나 컴퓨터 앞에 앉아 업데이트를 시작했어. 그런데 업데이트가 끝나자마자, 블로그에 접속할 수 없게 되었어.

아이: 왜요?

산타 할아버지: 알고 보니, 블로그가 사용하는 데이터베이스의 버전이 최신 버전과 호환되지 않았던 거야. 아이는 당황해서 밤새 데이터베이스를 업데이트했지만, 이번에는 다른 문제가 발생했어.

아이: 무슨 문제요?

산타 할아버지: 데이터베이스 마이그레이션 스크립트 중 하나가 실행되지 않았던 거야. 아이는 결국 데이터베이스 테이블을 수정해서 문제를 해결하고, 업데이트를 성공적으로 마쳤어.

아이: 다행이다!

산타 할아버지: 아이는 이번 일을 통해 몇 가지 교훈을 얻었어. 첫째, 소프트웨어 버전을 올릴 때에는 항상 의존하는 소프트웨어의 업데이트가 먼저 필요하지는 않은지 확인해야 한다는 거야. 둘째, 마이그레이션이 수반되는 업데이트에서는 데이터베이스 백업을 반드시 해야 한다는 거야. 셋째, 다운타임이 발생할 때 방문자들이 당황하지 않게 점검 페이지를 띄워두는 것이 좋단 말이야.

산타 할아버지는 아이가 졸리다고 생각해서 이야기를 마쳤어. 아이는 산타 할아버지의 이야기가 재미있었는지, 눈을 반짝이며 웃었어.

산타 할아버지: 자, 이제 잘 시간이다. 안녕!

아이: 안녕히 계세요!

산타 할아버지는 아이가 잠들 때까지 옆에 앉아 있었어. 그리고 아이가 잠들자, 조용히 컴퓨터를 끄고 집으로 돌아갔어.

산타 할아버지: (혼잣말로) 다음에 또 재미있는 이야기를 들려주어야지.

요약이 본문만큼 길어 보이는 건 기분 탓이다.

정상(Sang Jeong)

정상(Sang Jeong)

컴퓨터와 자동차를 좋아하는 대학생입니다. 공부와 연구 모두 열심히 하고 싶어요!
대한민국 부산 (Busan, South Korea)