250930 장애 대응
개발 •
장애 상황
오랜만에 블로그에 작업 하자마자 큰 장애를 만들어버렸습니다.
꽤 오랫동안 블로그에 아무런 작업 없이 레이어만 몇 개 추가되면서 장애 포인트가 생겼다가, 어제 진행한 작업 몇 개가 영향을 미쳐서 터져버렸습니다.

APM이 막 울어서 블로그에 접속해보니, 위와 같이 이미지가 로딩되지 않고 있었습니다.
그리고 콘솔을 보니, JS파일을 불러오지를 못하고 있더라고요.

장애 대응
신규 기능 작업 (25.08.09)
제 블로그에 cover image 대부분은 단색 배경에 아이콘이 중앙에 있는 형식입니다.
매번 이런 간단한 이미지 만들려고 이미지 편집 툴을 쓰는 게 아쉬워서, 간단하게 /api/thumbnail/image-emoji:⚠️-fontSize:72-backgroundColor:1c244a.svg
와 같은 형식으로 이미지를 생성할 수 있는 API를 하나 추가했습니다.
새로운 레이어 추가 (25.08.20)
최근 traffic-switcher라는 서비스를 추가했습니다 (관련 linkedin 게시글).
기존에는 NGINX에서 단순히 reverse proxy만 진행하고 있었습니다.
다만 이를 위해 NGINX 재시작을 위해 배포 스크립트가 과도한 권한을 갖는 것도, NIGNX 재시작 중 짧게 down time이 발생하는 것도 아쉬웠습니다.
이런 문제들을 해결하기 위해 작업을 진행하며, gzip 압축도 함께 진행하도록 하였습니다.
다만, 파일을 서빙하는 rustyfiles는 한 번 저장된 파일은 항상 file system에서 제공하기에, 큰 문제가 발생하지 않고 있었습니다.
신규 포스트 발행 (25.09.29)
새로운 글을 하나 발행하며, cover image로 api를 사용하였습니다.
기존에 svg 파일을 확장자로 사용하지 않아 문제를 인지하지 못했으나, API를 사용하며 svg 파일도 일반적인 png 파일처럼 resizing을 시도한다는 것을 알고, 이를 수정하는 코드를 함께 배포했습니다.
이때부터 APM이 울고 있고, 코드가 조금씩 깨져 있었을텐데, 조금 바빴던 탓에 이를 제대로 확인하지 못하고 있었습니다.
장애 인지 (25.09.30 17:46)
APM을 확인하고, 블로그에 접속해 상술했던 것처럼 이미지가 깨지고 있는 것을 확인했습니다.

이 화면을 확인하고, 처음에는 docker가 이상한 파일을 생성했다고 생각하고, cache를 소거하고 다시 빌드를 진행했습니다.
docker system prune
docker builder prune -a
혹시나 디스크 용량이 부족해서 문제였나 싶어 디스크 용량을 확인해 봐도, 아주 여유로웠습니다.
소소하지만 이 작업 덕분에 150GB 정도 쓰레기를 정리하긴 했습니다.
이후에 CDN 서버에 직접 접속해 파일을 확인하고, 파일 자체가 깨져있는 것을 확인했습니다.
추가로, 코드를 임의로 수정해 보다가, 제가 수정한 파일들만 깨지고 있다는 것도 확인했습니다.
CDN 서버 제거 (25.09.30 18:18)
나가봐야 할 일이 있어 더 오래 시간을 쓸 수 없다고 판단하고, 블로그가 CDN 서버를 사용하지 않고 동작하도록 수정하고, CDN 서버에 저장되어 있던 정적 파일 모두를 제거했습니다.
NEXT_PUBLIC_CDN_URL=
NEXT_PUBLIC_FILE_CDN_URL=
한 가지 간과했던 점이, 서버는 같지만 테스트 목적으로 파일 서버와 이미지 서버 환경 변수를 분리해 뒀는데, cdn-t.marshallku.com
주소만 보고 NEXT_PUBLIC_CDN_URL
만 공백으로 처리했었습니다.
NEXT_PUBLIC_FILE_CDN_URL
를 제거했어야 하는 거였기에, 이때부터 정상적으로 file system에 남아있던 파일까지 깨지기 시작했습니다.
문제 재인지 (25.09.30 22:47)
집에 복귀하자마자 블로그를 확인해보니, 전보다 훨씬 심한 수준으로 깨지고 있었습니다(당연히요).
파일 인코딩을 확인해보다가, 혹시 gzip 문제일까 싶어 rustyfiles가 gzip된 파일도 올바르게 처리하도록 수정했습니다(21:57).
추가로, 이미지 경로가 marshallku.com//images/images/2024/11/dockers.w500.png
와 같이 /
가 두 번 나오는 것도 발견하고, 함께 수정했습니다.
이러고 상황을 정리하다가, 배포를 하지 않고 있던 걸 인지하고 rustyfiles 2.1.1 버전을 배포했습니다(22:07).

와중에 의존성 수정하니 3만 년 동안 빌드를 하고 있어서, 이를 손봐야겠다는 생각도 좀 했습니다...
또다른 문제 발생 (25.09.30 22:31)

위 문제는 전부 해결되었으나, 이젠 또 JavaScript 파일이 아니라 경로를 저장해버리는 문제가 발생하였습니다.
목록 페이지에는 접근할 수 있으나, 글 상세 페이지에 아예 접근 자체가 안 되어버리는, 아까보다 조금 더 심각해진 상황이었습니다.
[...slug]/page-$hash.js
같은 파일이 깨져있어, 인코딩 문제일 것으로 판단했습니다.
let url = format!("{}{}", host, path);
기존에는 위와 같이 불러올 파일 주소를 가져오도록 하였으나, Axum이 자동으로 path를 URL-decode 해줘서 잘못된 경로로 요청이 가고 있어 이상한 파일이 저장되는 것이었습니다.
let encoded_path: String = path
.split('/')
.map(|segment| urlencoding::encode(segment).into_owned())
.collect::<Vec<_>>()
.join("/");
let url = format!("{}{}", host, encoded_path);
아주 간단하게 다시 인코딩만 해주도록 수정하고, rustyfiles 2.1.2 버전을 배포했습니다(25.09.30 22:44).
남은 문제
이제 정말 모든 문제가 해결되었으나, 기존에 잘못된 파일을 받아버린 사용자들은 깨진 파일을 1년동안 disk cache로 들고 있어야 한다는 문제가 남아있습니다.
단순히 파일 구성을 조금 바꿔서 해결하는 방향으로 쉽게 처리하려고 했으나, Next.js가 생각보다 hashing을 똑똑하게 하고 있는 탓에 확실하게 해결해야 했습니다.
처음 rustyfiles를 제작하며 테스트 목적으로 cdn-t.marshallku.com
이라는 도메인을 사용하고 있었는데, 이참에 이를 cdn.marshallku.com
으로 바꿔버리기로 결정했습니다. \

cdn.marshallku.com
의 A 레코드를 등록하고, 인증서를 발급했습니다.
제일 단순하게 처리하기 위해, 기존 인증서에 cdn.marshallku.com
을 추가하는 방향으로 진행했습니다(23:05).

23:18 경 배포가 완료되었고, 모든 장애가 종료되었습니다.
깨달은 점
포스트 하나 적으려다 여기까지 와버렸습니다.
오랜만에 작업하니 블로그 코드들에 대한 맥락이 희미해진 부분도 많았고, 블로그 하나 굴리려고 5개 이상의 서비스가 굴러가다보니 장애 포인트를 찾기도 꽤 어려워져버렸습니다.
사실 Rust 관련 서비스에 APM이 있어도 이번 건은 못 잡았겠지만, Rust API들에는 APM도 하나도 없어서, 짬 나면 바로 이를 추가해야겠다는 생각이 들었습니다.
추가로, 3만년 걸리는 의존성 full-build 시간도 고쳐봐야하지 않을까 싶습니다.
신규 기능을 추가할 때마다, 영향을 줄 수 있는 서비스들을 꼼꼼히 확인하고 작업하는 것이 중요하다는 것을 다시 한 번 느끼게 되었네요.
추가로, 열심히 APM 구축해둬도 바빠지니 확인을 못하는 이슈가 있어서...아주 치명적인 이슈 말고는 최대한 알림이 발생하지 않도록 하여 민감도도 조금 올려볼 생각입니다.