RSS듀오랩스
웹 인프라

Vercel 재배포 직후 500: 재배포 탓인지 값 탓인지 가르는 순서

작성자
듀오랩스 대표·7분 읽기

운영에 올릴 새 커밋은 없었습니다. maindevelop 이 같은 커밋이었고, 마지막 운영 배포는 그 커밋이 올라간 지 4분 뒤에 만들어져 있었습니다. 그래도 한 번 다시 올려 두자고 해서 vercel redeploy 로 같은 배포를 다시 만들었습니다. 2분 만에 Ready 가 떴고 도메인에 붙었습니다.

그리고 첫 화면이 500 이었습니다. 브라우저 콘솔에는 이렇게 떠 있었습니다.

Uncaught Error: An error occurred in the Server Components render.
The specific message is omitted in production builds to avoid leaking sensitive details.

재배포가 깨뜨린 것처럼 보이는 순간

방금 재배포했고 방금 500 이 났으니 둘을 잇고 싶어집니다. 저도 그랬습니다. 그런데 코드가 한 줄도 안 바뀐 재배포입니다. 코드가 같은데 결과가 다르다면, 다른 것은 배포에 들어간 값이거나 배포 밖의 무언가입니다.

그래서 재배포 이전 배포의 *.vercel.app 주소를 직접 쳐 봤습니다. 똑같이 500 이었습니다. 이 한 번의 확인이 진단의 방향을 바꿨습니다. 재배포는 죽어 있던 운영을 죽은 채로 한 번 더 만든 것이지, 살아 있던 운영을 죽인 것이 아니었습니다.

vercel ls 는 운영 배포를 시간순으로 다 보여주니, 하나 전 배포의 주소는 언제나 거기 있습니다. 재배포 직후 500 을 만나면 그 주소부터 여는 것이 가장 싼 확인입니다. 이전 배포가 멀쩡하면 재배포가 문제이고, 이전 배포도 죽어 있으면 재배포는 무죄입니다.

로그가 가리킨 곳, 그리고 이미 적어 둔 함정

vercel logs 로 새 배포의 런타임 로그를 열었습니다.

Error [DriverAdapterError]: (ENOIDENTIFIER) no tenant identifier provided
  (external_id or sni_hostname required)

이 오류는 낯설지 않았습니다. 지난주에 같은 오류로 운영이 죽었고, 그때 원인을 글로 남겼습니다. Supabase 의 커넥션 풀러는 여러 프로젝트를 한 호스트로 받기 때문에 어느 프로젝트인지를 계정 이름에서 읽습니다. 그래서 풀러로 붙을 때는 postgres 가 아니라 postgres.<프로젝트 ref> 여야 합니다. 직결은 호스트가 프로젝트별이라 postgres 가 맞고, 그 차이를 리포의 운영 가이드에도 적어 두었습니다.

적어 둔 것과 값이 맞는 것은 별개였습니다.

대조의 「일치」가 보증하지 않는 것

값의 정본은 볼트이고, 볼트에서 Vercel 로 밀어넣는 도구가 있습니다. 대조 명령을 돌리니 운영 환경 29개 키가 전부 최신 이었습니다. 볼트와 Vercel 이 같다는 뜻입니다. 저는 잠깐 여기서 안심했습니다.

그런데 이 명령이 확인하는 것은 같은가이지 맞는가가 아닙니다. 볼트에 든 값이 틀렸으면 배포처에도 틀린 값이 정확히 복사돼 있고, 대조는 그 상태를 "일치" 라고 부릅니다. 정합성 검사는 정합성만 보증합니다.

Vercel 에서 값을 되읽을 수는 없었습니다. Sensitive 로 넣은 변수는 vercel env pull 로도 자리표시자만 내려옵니다. 그래서 볼트에서 파일로 내려받고, 값 자체는 찍지 않고 계정 이름과 호스트만 파싱해서 봤습니다.

호스트 계정
DATABASE_URL pooler.supabase.com:6543 postgres
DIRECT_URL pooler.supabase.com:5432 postgres.<ref>

두 값이 같은 풀러 호스트를 보는데 하나만 접미사가 있었습니다. 지난주에 고친 것은 DIRECT_URL 쪽이었고, 런타임이 쓰는 DATABASE_URL 은 옛 계정 그대로였습니다. 같은 함정에 두 번 빠진 것이 아니라, 한 번 빠진 자리에서 반만 나온 것이었습니다.

값 사고에 롤백을 쓰지 않은 이유

지난주 글의 결론 하나가 이번에 그대로 쓰였습니다. 값이 원인일 때 롤백은 되돌려 주지 않습니다. 이전 배포가 같은 틀린 값을 들고 있으니 돌아가도 500 이고, 롤백은 도메인 별칭을 그 배포에 고정해서 뒤에 오는 수정을 화면에서 가립니다. 이번엔 이전 배포도 500 인 것을 이미 확인했으니 롤백은 처음부터 선택지에서 빠졌습니다.

복구 경로는 하나입니다. 볼트의 DATABASE_URL 계정을 postgres.<ref> 로 고치고, 볼트에서 Vercel 로 밀고, 다시 배포합니다.

배포 시점에 굳는 환경변수, 그리고 빌드 캐시 옵션

Vercel 환경변수는 프로젝트에 저장되지만 각 배포는 빌드 시점의 값을 들고 굳습니다. 값만 고치고 아무것도 안 하면 지금 서빙 중인 배포는 여전히 옛 값을 씁니다. 그래서 값을 고친 뒤에는 새 커밋을 푸시하든 같은 배포를 다시 만들든, 배포가 한 번 새로 일어나야 합니다. 이번처럼 코드 변경이 없으면 푸시할 것이 없으니 재배포가 맞습니다.

대시보드에서 Redeploy 를 누르면 "Use existing Build Cache" 체크박스가 나옵니다. 이 옵션은 이전 빌드의 의존성 설치 결과와 Next.js 빌드 캐시를 다시 쓰겠다는 뜻이고, 환경변수는 캐시와 무관하게 빌드 때 새로 읽힙니다. 서버 런타임 값을 고친 재배포라면 켜 두어도 새 값이 들어갑니다.

끄는 것이 맞는 경우는 따로 있습니다. NEXT_PUBLIC_* 처럼 빌드 산출물 안에 박히는 값을 바꿨는데 화면에 반영이 안 될 때, 그리고 의존성이나 빌드 설정이 꼬여서 깨끗한 상태에서 다시 돌리고 싶을 때입니다. 이번 사고는 둘 다 아니었습니다.

다음에 다르게 할 것

재배포 직후 500 은 이전 배포 주소를 먼저 엽니다. 코드가 같은 재배포라면 재배포가 원인일 가능성은 거의 없고, 그 확인은 curl 한 줄입니다.

대조 결과의 "일치" 를 "정상" 으로 읽지 않습니다. 볼트가 정본이면 볼트가 틀린 순간 모든 배포처가 같은 방식으로 틀립니다. 값이 맞는지는 실제로 붙어 보는 것으로만 확인됩니다.

같은 성격의 키는 같이 고칩니다. DATABASE_URLDIRECT_URL 은 같은 호스트를 보는 한 쌍이고, 하나를 고치면서 다른 하나를 안 본 것이 이번 사고의 직접 원인이었습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.