개인 퍼포스 장애 대응 보고
퍼포스를 개인용 버전 관리에 사용합니다. 이 환경은 신뢰할 수 있게 동작하지만 때때로 문제를 일으킵니다. 이번에는 퍼포스 데이터베이스와 아카이브가 한 순간 사라졌다고 생각해 깜짝 놀랐습니다.

비프로그래머 입장에서 사용할 형상관리도구를 찾아 이것 저것 시도해보다가 결국 퍼포스를 선택했습니다[^돌고 돌아 퍼포스]. 이 도구는 개발을 시작한지 거의 30년이 되어 가지만 전문 엔지니어들 사이에는 널리 알려지지 않은 것 같습니다. 게임 업계에서는 퍼포스를 자주 사용했는데 가장 큰 이유는 코드 이외에 바이너리 에셋이 굉장히 많고 퍼포스를 제외한 어지간한 도구는 이 수많은 바이너리 에셋을 원활하게 처리해 내지 못하기 때문입니다. 사실 인공지능 붐이 일면서 깃 LFS도 이전 시대보다는 훨씬 쓸만해진 것 같은 분위기이지만 한 번에 몇 기가 짜리 바이너리 파일을 올리는 것과 수 십 메가 짜리 바이너리 파일 수 만 개를 수 만 리비전에 걸쳐 올리는 건 조금 다른 이야기입니다. 그 상황에서 브랜치를 전환하거나 오래된 리비전으로 돌아가려고 하면 어지간한 도구는 이 상황을 감당하지 못합니다. 하지만 퍼포스는 이 상황을 감당해 냅니다. 서로 다른 여러 직군이 쏟아내는 코드를 담은 전통적인 텍스트 포멧 뿐 아니라 언리얼 데이터 에셋, 아트 에셋 원본 따위를 끝없이 서브밋해도 군소리 없이 돌아갑니다. 프로그래머가 아닌 입장에서 텍스트 파일 보다는 결국 바이너리 파일을 더 자주 만드는 입장이어서 깃보다는 퍼포스를 선택할 수밖에 없었습니다. 퍼포스 서버를 구동하는 건 꽤 단순합니다. 처음에는 누군가 만들어 둔 도커 이미지를 사용하다가 제 입맛에 맞게 조금 수정해 사용하기 시작[^처음으로 도커 이미지를 만들어 보고 배운 것]했고 이제 2년 정도 별 무리 없이 운영하고 있습니다. 퍼포스가 갑자기 개인 사용자들에게 돈을 받기 시작한다면 이야기가 달라질 수 있지만 사용자 다섯 명, 워크스페이스 스무 개 까지는 무료로 사용할 수 있는 한은 퍼포스를 계속해서 사용할 것 같습니다.
사실 이 말에는 거짓말이 조금 포함되어 있습니다. 도커 환경에서 퍼포스 서버를 구축하는 건 쉬운 일입니다. 그냥 이미지를 받아 적당히 컴포즈 파일을 만들어 실행하기만 하면 됩니다. 하지만 그 상태를 지속적으로 유지하며 가끔 모든 디포 파일과 모든 데이터베이스가 멀쩡한지 검사하고 백업하고 퍼포스 서버 버전 업데이트에 대응하고 너무 오래 걸리는 프로세스를 취소하는 등의 유지보수 작업을 해야 합니다. 또 매일 새벽 데이터베이스 체크포인트 파일을 만들어 적당히 안전한 곳으로 백업하는 것도 신경 써야 합니다. 하지만 제가 퍼포스로부터 얻는 이득이 이러한 유지보숭 드는 수고보다는 훨씬 크다고 생각하기 때문에, 또 퍼포스 서버 유지관리를 통해 집과 회사에서 모두 사용하는 이 도구에 대한 이해를 늘릴 수 있기 때문에 유지보수 부하에도 불구하고 퍼포스 서버를 계속해서 유지하고 있습니다. 그러니까 만들기는 쉽지만 오랜 시간에 걸쳐 안정적으로 사용하는데는 수고가 듭니다. 이 과정은 그리 쉽지만은 않습니다. 하지만 해결 불가능하지도 않습니다. 가령 지난번에는 데이터베이스 파일이 깨져서 서버가 죽지는 않았지만 서브밋 프로세스가 영원히 끝나지 않는 상황을 맞이했습니다[^퍼포스 서버 데이터베이스 오류 문제해결]. 전문적인 IT 엔지니어라면 문제가 생긴 정확한 파일을 찾고 또 그 파일만 체크포인트로붜 복원해낼 수 있었을는지도 모릅니다. 또 전설로 전해오는 데이터베이스 복구 사례처럼 깨진 파일의 남은 부분을 읽어들여 온전한 레코드를 복원하는 것 같은 아슬아슬한 묘기를 부릴 생각을 했을는지도 모릅니다. 하지만 저는 이 중 그 어느 것도 할 수 없었기 때문에 한 시간에 한 번 수행되는 로컬 백업으로부터 퍼포스 데이터베이스 파일 전체를 끄집어내 현재 데이터베이스에 덮어 씌운 다음 재시작 해 문제 상황에서 빠져나올 수 있었습니다.
이 경험을 통해 한 시간 단위 로컬 백업이 얼마나 중요한지 다시 한 번 인식하게 되었습니다. 이 때나 지금이나 똑같이 로컬 백업은 도커 컨테이너가 돌고 있는 맥OS의 타임머신을 사용하고 있습니다. 타임머신 백업은 단순하기는 하지만 여러 가지 문제가 있습니다. 하지만 또 맥에서 이만큼 단순하게 운용할 수 있는 로컬 백업 도구가 드물어 사용하고 있는데 한 시간에 한 번 백업이 동작한 덕분에 데이터베이스를 복원하더라도 유실된 데이터가 그리 크지 않아 데이터 유실로부터 문제를 겪지는 않았습니다. 또 체크포인트 파일로부터 데이터베이스를 복원하는 과정을 연습해 둬야겠다고 생각하기는 했는데 체크포인트 파일은 여전히 새벽마다 만들어지지만 백업이 버티고 있는 이상 체크포인트 파일로부터 데이터베이스를 복원할 일은 없지 않을까 싶고 또 문제 상황도 아닌데 굳이 체크포인트로부터 데이터베이스를 복원해낼 연습을 할 필요는 없지 않나 하는 생각이 들어 지금까지도 체크포인트로부터 데이터베이스 복원 연습을 하지는 않았습니다. 그리고 지난 퍼포스 서버 장애로부터 5개월이 지난 오늘, 퍼포스 서버를 사용하려고 보니 서버가 타임아웃을 띄우고 있었습니다. 새로운 장애가 발생한 겁니다. 오늘은 이 장애 상황을 설명하고 장애 원인, 대응, 해결 방법, 재발 방지 대책을 소개하겠습니다. 이건 누군가 읽기를 원하는 글이라기보다는 미래의 제가 참고할 목적이 더 큽니다.
일단 상황은 출근한 다음 발생했습니다. 모든 도커 스택을 돌리는 맥미니 서버는 매일 새벽 3시에 도커 데몬을 재시작합니다. 사실 이 모든 컨테이너가 몇 주에 걸쳐 안정적으로 동작하기도 한다는 사실을 모르지 않지만 종종 cloudflared가 모든 엣지로부터 접속이 끊긴 채 이 상태를 복구하지 못하고 아무런 상태 변화도 없이 마치 멀쩡히 동작하는 것처럼 동작하고 있을 때가 있습니다. 헬스체크로는 이 상태를 감지하기 어려운데 웹서비스에 직접 접근해 봐야만 상황을 파악할 수 있습니다. 그래서 도커 헬스체크 대신 uptime-kuma로 상태를 확인하고 있는데 이 서비스 역시 같은 서버에서 돌고 있고 또 이 서버 역시 클라우드플레어 뒤에 있기 때문에 종종 문제를 발견할 수 없을 때가 있었습니다. 그러니까 이 서버에서 돌아가는 웹사이트를 감시하다가 웹사이트가 1033 에러를 내면 저에게 메신저로 알려줘야 하는데 이 상황에서 uptime-kuma가 사용하는 cloudflared도 장애가 생기면 저는 아무 알림도 받지 못하는 사애가 됩니다. 사실 이 문제를 해결하려면 감시 서비스는 서버 외부에 있어야 하지만 이 또한 돈이라 외부 서버를 사용하지 않는 범위 안에서 인프라를 구축하다 보니 이 상태를 벗어날 방법이 없었습니다. cloudflared 뿐만 아니라 도커 데스크탑 자체가 크래시되기도 했는데 어떤 문제로 크래시되는지 알 수가 없었습니다. 문서에는 메모리가 부족한 상태 등의 원인을 지목하고 있었는데 모니터링에 따르면 그런 일은 일어나지 않았습니다. 갑자기 제가 사용하는 모든 서비스가 동시에 응답하지 않는 상태가 되는데 이 때 원격으로 서버를 살펴보면 도커 데스크탑이 크래시된 채 복구되지 않은 모양으로 멈춰 있었습니다. 그래서 이런 상태를 겪기보다는 선제 대응으로 새벽 3시에 도커 데몬을 재시작했는데 이후 제가 재시작하는 그 때 말고는 서비스 장애가 발생하지 않아 꽤 성공적이라고 생각하고 있습니다.
그런데 퍼포스 서버와 파일을 동기화하려고 보니 p4v가 타임아웃을 띄운 채 서버에 접속하지 않고 있었습니다. 다른 웹 서비스는 클라우드플레어 뒤에 있는데 퍼포스 서버는 클라우드플레어 뒤에 둘 수 없었습니다. 뭔가 가능할 것처럼 보이고 또 문서에소도 가능할 것처럼 이야기하지만 실제로는 동작하지 않았습니다. 처음에는 퍼포스 서버를 인터넷에 노출시켰는데 아무리 암호화 한다 하더라도 개인 파일이 몽땅 올라가 있는 퍼포스 서버를 인터넷에 노출시키는 건 썩 내키지 않는 일이었습니다. 고민 끝에 테일스케일을 사용하기로 합니다. 퍼포스 서버를 테일스케일 뒤로 옮기자 여러 가지가 단순해졌습니다. 일단 서버를 인터넷에 노출 시키기 위한 네트워크 설정이 필요 없어졌습니다. 또 이론적으로는 테일스케일이 인터넷 구간의 암호화를 처리해 주므로 퍼포스 서버에 SSL 환경을 설정할 필요가 없습니다. 하지만 굳이 암호화를 제거할 필요도 없기 때문에 그대로 뒀습니다. 그러니까 퍼포스 서버에 접근하려면 전적으로 테일스케일을 통해야 한다는 이야기입니다. 퍼포스 서버는 도커 컴포즈에 의해 완전히 격리된 네트워크 환경에서 테일스케일과 함께 시작하고 퍼포스 서버는 오직 테일스케일을 통해서만 외부와 통신합니다. 덕분에 서버 주소를 인터넷에 노출할 필요 없이 로컬 네트워크 상의 호스트네임을 사용할 수 있습니다. 이 상황에서 퍼포스 서버가 타임아웃을 띄우고 있다는 것은 두 가지 원인 밖에 없을 것 같습니다. 하나는 어떤 이유로 퍼포스 서버가 크래시됐거나 테일스케일 컨테이너에 문제가 생긴 겁니다. 처음에는 그냥 도커 컴포즈 스택을 재시작하면 문제가 해결되지 않을까 싶어 바로 재시작했지만 문제는 해결되지 않았습니다. 다음은 테일스케일 차례입니다.
테일스케일 로그를 살펴보니 인증에 실패했다는 메시지가 남아있습니다. 테일스케일 컨테이너는 테일스케일 웹사이트에서 발급 받은 키를 사용하는데 만료 시점을 비활성화 할 수 있는 다른 환경과 달리 키를 사용하면 반드시 90일 동안만 사용하도록 해야 합니다. 보안 때문이라는 사실을 알기는 하지만 좀 귀찮다는 생각입니다. 아마 이 과정 자체를 자동화 할 수 있을 것 같은데 거기까지 제대로 생각해보지는 않았습니다. 마지막으로 키를 발급한 때로부터 벌써 90일이 지났나? 아직 안 되지 않았나? 싶었지만 어쨌든 키는 만료됐고 해결 방법은 재발급하는 것입니다. 재발급 과정은 간단했고 컴포즈 파일과 같은 디렉토리에 있는 .env 파일에 키를 업데이트하고 테일스케일을 재실행합니다. 이번에는 인증을 통과해 시작되었고 셧다운 시그널을 기다린다는 메시지가 나타납니다. 아마 접속 문제가 해결되었을 겁니다. 하지만 이 다음에 시도한 퍼포스 서버 접속도 실패했는데 아까와는 양상이 달랐습니다. 아까는 타임아웃 에러를 내고 있었습니다. 타임아웃이라면 서버 크래시나 테일스케일의 라고 예상할 수 있었지만 이런 오류는 전혀 예상하지 못했습니다. 퍼포스 서버가 응답하는 결과를 받을 수는 있었습니다. 이건 테일스케일이 멀쩡히 동작한다는 의미입니다. 하지만 서버가 돌려준 ‘no such user’는 이게 도대체 무슨 상황인지 헛갈리도록 만들기에 충분했습니다. 그러니까 퍼포스 서버는 어제까지 멀쩡히 사용하던 제 계정이 없다고 주장하고 있는 겁니다.
‘어?’ 하고 의자 위에서 자세를 고쳐 앉은 다음 p4v를 껐다가 다시 켜 봅니다. 이번에도 그런 유저는 없답니다. 퍼포스 서버 터미널에 접근해 p4 users, p4 depots, p4 clients를 차례로 입력해봅니다. 각각 이 서버의 모든 유저, 이 서버의 모든 디포, 이 서버의 모든 클라이언트 목록을 보는 명령어들입니다. 모든 명령어는 제가 예상한 유저 목록, 디포 목록, 클라이언트 목록을 돌려주는 대신 마치 처음 만들어진 서버인 것처럼 행동했습니다. 유저는 관리자 유저 뿐이었고 디포는 서버 관리용 기본 디포 뿐이었으며 등록된 클라이언트는 없었습니다. 그러니까 지금 이 서버는 제가 지난 2년에 걸쳐 사용하던 모든 디포 파일과 데이터베이스가 전부 사라진 상태인 겁니다. 크게 당황하지는 않았습니다. 만약 어떤 원인에 의해 그 모든 것이 유실된다 하더라도 타임머신 백업이 로컬에 서로 다른 하드디스크 두 개에 번갈아가며 저장되어 있었습니다. 또 Arq Backup을 사용한 세 번째 로컬 백업도 있습니다. 어떤 이유로 이 모든 백업을 사용하지 못한다면 돈이 좀 들겠지만 Arq Backup을 사용하는 S3 Glacier Deep Archive 백업도 있었습니다. 그러니까 근본적으로 모든 퍼포스 서버 데이터를 유실할 일은 없습니다. 다만 로컬 백업으로부터 복원한다면 몇 시간 정도, 오프사이트 백업으로부터 복원한다면 복원 요청에 48시간, 다운로드에 몇 시간, 복원에 몇 시간 정도가 걸릴 겁니다. 그러니까 데이터 유실 가능성은 거의 없습니다. 다만 시간이 좀 걸리고 이 시간 동안 서비스는 중단된 상태여야만 합니다. 하지만 이런 상태가 발생한 원인을 짐작할 수가 없었습니다. 지난번 정전 대응 때 품질이 나쁜 케이블을 사용했다가 스토리지가 언마운트되면서 sqlite 데이터베이스파일이 깨진 적이 있었는데 이 때는 백업이 없을 수밖에 없는 상황이어서 상당히 당혹스러웠습니다. 하지만 품질이 나쁜 케이블 때문이었다는 정확한 원인을 파악할 수 있었습니다. 하지만 이번에는 갑자기 퍼포스 서버의 모든 파일이 사라지고 또 모든 데이터베이스가 사라질 만한 어떤 징조도 느끼지 못했습니다. 스토리지 기계들은 모두 멀쩡했고 정전이 일어난 것도 아니고 도커 데스크탑은 일정대로 새벽 3시에 안전하게 재시작 되었으며 도커 수준의 헬스체크에서 아무런 문제도 발견되지 않았습니다. 하지만 지금 퍼포스 서버는 마치 새로 만든 서버처럼 그 어떤 데이터도 없다고 주장했습니다.
이 상황에서 문제의 원인을 눈치채기까지는 시간이 꽤 걸렸습니다. 사실 원인은 굉장히 간단했지만 퍼포스 서버의 데이터베이스 디렉토리와 디포 아카이브 경로를 찾아보고 정말로 데이터베이스 파일들이 방금 생성된 것처럼 작고 빠른 상태이며 디포 아카이브 디렉토리에는 아무것도 없는 것을 보고 이 상황 자체가 힌트라는 점을 생각하기는 쉽지 않았습니다. 데이터베이스 파일과 디포 파일 모두가 유실되었고 이들을 복원하려면 먼저 상대적으로 작은 데이터베이스 파일부터 복원하기 시작해야 한다고 생각했습니다. 데이터베이스 파일은 약 10기가 정도인데 이 정도는 순식간에 복원할 수 있었고 디포 아카이브 파일이 없어도 일단 서버가 실행된다는 것을 알고 있었습니다. 다만 클라이언트에서 업데이트를 요청하거나 서브밋을 시도할 때 이전 리비전의 파일을 찾을 수 없어 라이브라리안이라는 모듈이 에러를 낸다는 것도 알고 있었습니다. 그러니까 일단 데이터베이스를 복원한 다음 서버를 시작하고 디포 파일은 시간을 두고 천천히 복원하면 될 것 같았습니다. 일단 데이터베이스 파일을 복원하면서 도대체 이 상황이 왜 일어났는지 생각해봤습니다. 케이블은 멀쩡했습니다. 스토리지 장치도 멀쩡했습니다. SSD가 과열된 것일까요? 그런 것도 아닌 것 같아 보입니다. 물론 SSD 온도가 아주 낮지는 않았지만 또 스로틀링에 걸릴 정도로 높았던 것도 아닙니다. 퍼포스 서버 로그는 모든 것이 멀쩡했다고 말하고 있었고 문제는 오직 테일스케일에만 있었습니다. 하지만 테일스케일의 문제 때문에 퍼포스 서버의 모든 데이터가 사라졌다고 생각하기는 어려웠습니다. 이쯤 되니 혹시 보안 사고가 아닐까 하는 생각도 들었습니다. 하지만 퍼포스 서버는 테일스케일 뒤에 있었고 테일스케일 쪽에서도 뭔가 문제가 생긴 것 같아 보이지 않았습니다. 모르는 클라이언트가 나타나 있다든지 한 것도 아니었습니다. 데이터베이스 파일을 복원하고 퍼포스 서버를 재시작한 다음 p4v로 다시 접속을 시도해 봅니다. 이번에도' no such user' 에러를 냅니다. 아니 방금 데이터베이스를 복원했는데도 말입니다. 이쯤 되니 헛웃음이 나기 시작합니다.
원인은 다른데 있었습니다. 얼마 전 Hoarder를 위한 전용 스토리지를 추가했습니다. 현재 ‘Karakeep’이라는 이름으로 리브랜딩 된 이 앱은 원래 Pocket과 같이 웹페이지를 스크랩 해 나중에 읽을 수 있는 앱입니다. 그런데 유튜브 영상을 스크랩하면 영상을 별도로 저장해 나중에 유튜브에서 영상이 내려갈 경우 계쏙해서 접근할 수 있는 강력한 장점이 있습니다. 그래서 이 기능을 적극적으로 사용했는데 이는 필연적으로 스토리지 부족 문제를 일으켰습니다. 그래서 홉랩에서 운영하는 모든 서비스 중 가장 많은 스토리지를 요구하는 p4d를 단일 SSD에 두고 Hoarder를 포함한 나머지 모든 서비스들을 새 SSD를 추가해 이쪽으로 옮겼습니다. 그런데 이 과정에서 초기에 한동안 새 SSD가 불안정한 모습을 보이면서 이 스토리지에 있던 나머지 모든 서비스가 함께 일시적인 여러 차례의 중단을 겪었습니다. 저 개인이 사용하는 서비스야 별 상관 없지만 블로그나 마스토돈 같은 서비스는 저 혼자 사용하지 않기에 문제가 있다고 생각했고 이들은 스토리지를 많이 요구하지도 않기에 맥미니 서버의 로컬 스토리지로 옮깁니다. 그래서 p4d와 Hoarder는 각각 단일 SSD에서 구동되고 나머지 모든 서비스는 맥미니 로컬 스토리지에서 구동되는 상태가 되었습니다. 그런데 이 과정에서 도커 컴포즈 파일 버전 관리를 위해 기존 별도 SSD에 있던 p4d의 도커 컴포즈 파일을 한 벌 복사해 퍼포스 하위에 뒀는데 이게 바로 문제의 원인이었습니다. 빨리 퍼포스 서버를 복구해야 한다는 생각으로 제가 p4d가 구동되는 전용 SSD 경로가 아니라 별도로 복사해 둔 다른 경로에서 퍼포스 서버를 실행한 겁니다. 원래 퍼포스 서버는 시작할 때 기존 데이터베이스가 없으면 신규 서버 설정 상태로 들어가 서버 이름 등을 묻는 인터랙티브 모드가 됩니다. 아마 이렇게 동작했다면 제가 잘못된 경로의 서버를 실행하려고 한다는 사실을 바로 눈치 챘을는지도 모릅니다. 하지만 지금 사용하는 도커 이미지는 데이터베이스와 아카이브를 찾을 수 없으면 바로 새 서버를 만들어 실행해버리는 스크립트를 포함하고 있습니다. 그래서 잘못된 경로, 그러니까 데이터베이스 파일도 없고 아카이브도 없는 잘못된 경로에서 서버를 실행하자 시작 스크립트가 그냥 새 서버를 만들어 버린 겁니다. 이 서버는 주소도 똑같고 이름도 똑같고 심지어 SSL 핑거프린트도 똑같아서 이 상황을 눈치채지 못한 저에게는 데이터베이스와 모든 파일이 증발한 것처럼 보였던 것이었습니다.
‘설마 정말 이게 원인이라고?’ 싶은 생각에 떨리는 손으로 올바른 경로로 전환한 다음 컨테이너 스택을 시작합니다. 이번에도 똑같은 이름의 서버가 똑같은 주소에 똑같은 SSL에 기반해 실행됩니다. 그리고 커맨드라인 클라이언트로 똑같이 접속해 p4 depots를 실행하자 이번에는 디포가 정상적으로 나타납니다. 유저도, 클라이언트도 모두 제가 알던 그대로입니다. 서버 측의 커맨드라인 클라이언트에 p4 trust 명령을 넣어 핑거프린트를 승인한 다음 한 숨 돌린 다음 정상적으로 퍼포스를 사용하기 시작했습니다. 정말 아무런 징조 없이 모든 것이 한 방에 완전히 사라진 것처럼 보였기 때문에 상당히 놀랐습니다. 하지만 사라진 것은 아무것도 없었고 단지 제가 잘못된 경로에 있는 똑같은 서버를 실행했고 이 서버의 시작 스크립트는 잘못된 경로에 있으면 그냥 새로운 서버를 만들어 버렸으며 이 결과가 이전 서버와 전혀 다르지 않아 눈치채는데 시간이 걸렸습니다.
다시 비슷한 문제를 겪지 않기 위해 먼저 전용 스토리지를 사용하는 Hoarder와 p4d를 나머지 서비스들의 도커 컴포즈 파일 버전 관리에 사용하는 맥미니 로컬 스토리지 하위로 경로를 옮겼습니다. 하지만 이 두 서비스 모두 로컬 스토리지로 옮기기에는 너무 거대한 크기여서 도커 컴포즈 파일과 설정 파일만 가져오고 나머지 데이터는 각자의 SSD에 그대로 두고 볼륨 경로만 수정했습니다. 사실 이 둘을 제외한 나머지 서비스들은 볼륨 경로가 도커 컴포즈 파일이 있는 경로의 하위로 설정되어 있었고 Hoarder와 p4d도 각자의 SSD에 같은 방식으로 설정되어 있었지만 이제 이 두 서비스는 나머지와 달리 절대경로로 볼륨을 설정하도록 수정했습니다. 대신 각각의 SSD 에 있던 도커 컴포즈 파일을 제거해 잘못된 경로로부터 실행되지 않도록 했습니다. 이제 모든 서비스의 도커 컴포즈 파일과 설정 파일이 버전 관리 대상이 되었고 어설프게 다른 실행 위치가 남아 있는 상태로부터도 벗어났습니다. 이제 같은 문제를 겪지는 않겠지만 뭔가를 실행하거나 재실행하기 전에 항상 hostname, pwd로 현재 어느 박스의 어느 경로에 있는지 확인하는 습관을 들이기로 마음 먹었습니다.