요금제를 올려도 아키텍처는 살 수 없다

무료 티어에서 걸린 문제들을 Pro 요금제로 올리면 몇 개가 사라질까. 세어보니 용량 문제는 전부 지워졌는데 블로커는 오히려 하나 늘었다. 돈으로 사는 것과 못 사는 것의 경계에 대한 기록.

지난 글에서 개인 프로젝트를 무료 티어 PaaS로 옮길 때 걸리는 것들을 정리했다. 결론은 "진짜 제약은 용량이 아니라 실행 모델"이었다.

그럼 돈을 내면 어떻게 되나. 실행 시간 제한이 풀리고, 메모리를 더 쓸 수 있고, DB 용량이 몇 배가 되고, 자동 백업이 붙는다. 목록이 얼마나 줄어들까.

Pro가 지워주는 것

먼저 좋은 소식. 용량 계열 문제는 깔끔하게 사라진다.

무료 티어에서의 문제 | Pro에서
함수 실행 시간 제한 (PDF 타임아웃 위험) | 해결 — 상한이 크게 올라가 콜드 스타트 + 렌더 여유 확보
함수 메모리 | 해결 — 브라우저 렌더를 감당할 만큼 상향 가능
DB 용량 / 방문 로그 누적 | 해결 — 용량이 몇 배. 로그 정리는 위생 문제로 격하
비활성 시 일시정지 | 해결 — Pro는 정지되지 않음
자동 백업 부재 | 해결 — 관리형 자동 백업이 기존 스케줄러를 대체
대역폭 | 해결 — 개인 프로젝트 규모에서는 비고려

지난 글의 표에서 "요금제로 해결되나 = 예"였던 항목이 전부 지워졌다. 여기까지는 예상대로다.

Pro가 못 지워주는 것

그리고 나머지는 하나도 지워지지 않았다.

브라우저 조달 — 성격이 다른 블로커

무료 티어 검토 때는 "브라우저 바이너리가 커서 번들 제한을 넘는다"가 문제라고 봤다. 그래서 Pro가 제한을 올려주면 되는 줄 알았다.

컨테이너 빌드 설정을 다시 읽고서 생각이 바뀌었다.

RUN apk add --no-cache chromium ... font-noto-cjk
ENV PUPPETEER_SKIP_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

브라우저를 npm으로 받지 않고 있었다. OS 패키지 매니저로 설치해서 시스템 바이너리를 가리키고 있었다. npm 다운로드는 아예 꺼둔 상태다.

관리형 PaaS의 함수 런타임에는 그 패키지 매니저도, 시스템 브라우저도 없다. 다운로드 스킵 설정을 그대로 두면 런타임에 브라우저가 존재하지 않아 실행 시점에 바로 실패한다. 설정을 빼면 npm이 브라우저를 받으면서 번들 용량 문제로 돌아온다.

어느 쪽이든 서버리스용 경량 브라우저 패키지로 코드를 다시 써야 한다. 이건 요금제로 사는 게 아니다.

한글 폰트 — 새로 발견한 블로커

위 설정을 다시 보면 font-noto-cjk도 같이 깔고 있다. 한글 PDF를 만들려면 당연히 필요하다.

서버리스용 경량 브라우저 패키지들은 CJK 폰트를 싣지 않는다. 용량 때문이다.

그래서 브라우저를 성공적으로 띄우는 데 성공해도, 한글 이력서 PDF가 두부(□□□)로 나온다.

이 버그의 성격이 고약하다.

로컬 컨테이너에서는 절대 재현되지 않는다. 폰트가 깔려 있으니까.

빌드도 통과하고 함수도 정상 실행되고 PDF 파일도 정상 생성된다. HTTP 200이 떨어진다.

파일을 열어봐야 알 수 있다. 자동화된 테스트로 잡기 어렵다.

요금제와 아무 관계가 없다.

무료 티어 검토 단계에서는 이 문제를 못 봤다. 빌드 설정까지 읽고 나서야 나왔다. 블로커가 하나에서 둘로 늘어난 것이다.

멀티 인스턴스 — Pro도 인스턴스 1개를 보장하지 않는다

이게 핵심이다.

renderLock, in-memory rate limit 버킷, PDF 캐시, 상태 점검 캐시. 모두 "프로세스가 하나"라는 전제 위에 서 있다.

Pro 요금제는 인스턴스가 하나라고 약속해주지 않는다. 요금제는 인스턴스 하나가 쓸 수 있는 자원의 크기를 사는 것이고, 인스턴스가 몇 개 뜨는지는 바꾸지 않는다. 오히려 트래픽이 늘면 더 뜨는 게 관리형 PaaS의 존재 이유다.

최근에는 인스턴스 하나가 여러 요청을 동시에 처리하도록 하는 실행 모드도 있어서 상황이 완화되긴 한다. 하지만 완화는 보장이 아니다. "대체로 하나의 인스턴스에 모인다"에 메모리 폭발 방어를 걸어두는 건 설계가 아니다.

여기에 함정이 하나 더 있다. Pro의 넉넉한 메모리는 이 문제를 더 늦게 드러나게 만든다. 무료 티어라면 동시 렌더 두 개가 겹쳐서 금방 죽었을 텐데, 메모리가 넉넉하면 한동안 잘 돌아간다. 그러다 트래픽이 몰리는 날 처음 터진다.

자원을 늘려서 증상만 미룬 셈이다.

나머지는 요금제와 무관한 코드 작업

보안 헤더를 프록시 설정에서 프레임워크 설정으로 이전

헤더 위조로 rate limit을 우회하는 문제 처리

커넥션 풀러 경유로 DB 연결 방식 변경

회원가입 플래그 확인

컨테이너 전용 빌드 옵션 제거

이 중 하나는 오히려 쉬워진다. 신뢰할 수 없는 헤더 대신 플랫폼이 제공하는 검증된 클라이언트 IP를 쓰면 되기 때문이다. 관리형 환경으로 옮기는 게 이 항목에서는 이득이다.

재계산 결과

지난 글에서 정리한 10가지를 성격별로 묶어 다시 세면 이렇게 된다.

구분 | 항목 수 | Pro에서
실행 모델 (브라우저 조달, 렌더 직렬화, rate limiter, 캐시) | 3 | 남음 — 여기에 한글 폰트가 추가
신뢰 경계 (헤더 위조, 회원가입 플래그) | 2 | 남음
설정·연결 이전 (보안 헤더, DB 커넥션 방식) | 2 | 남음
용량·운영 (로그 누적, 일시정지, 백업 부재) | 3 | 사라짐

Pro는 용량 문제를 지우고 아키텍처 문제만 남긴다. 그리고 코드를 더 깊이 읽는 과정에서 블로커는 오히려 하나 늘었다.

요금제를 올리는 것으로 마이그레이션 난이도가 낮아지지 않았다. 난이도의 종류가 바뀌었을 뿐이다. "용량이 모자란다"에서 "코드를 고쳐야 한다"로.

그래서 옮기지 말라는 건가

아니다. 판단 기준이 명확해졌다는 게 이 검토의 성과다.

옮겨서 얻는 것은 서버 관리에서 손을 떼는 것, 자동 스케일링, 글로벌 엣지 배포, 그리고 배포 파이프라인을 직접 관리하지 않는 것이다. 이게 필요하면 위 다섯 개를 처리하면 된다. 며칠 작업이고, 한 번 하면 끝난다.

반대로 지금 내 상황을 보면, 단일 서버 컨테이너 구성은 이 프로젝트에 이미 잘 맞는다. 프록시 설정도 배포 스크립트도 이미 있다. 남은 작업 목록 전체가 "서버리스라서 필요한 것"이고, 단일 박스를 유지하면 작업량이 0이다.

두 요금제를 합치면 월 비용도 개인 이력서 사이트치고는 가볍지 않다. 서버 관리에서 손을 떼는 값으로 그걸 낼 만한가는 각자의 상황에 따라 다르다. 나는 지금 당장은 아니라고 결론 내렸다. 다만 트래픽 패턴이 바뀌거나 여러 사람이 붙게 되면 다시 계산할 것이다.

남는 교훈

요금제는 용량을 사는 것이고, 아키텍처는 사는 게 아니다.

마이그레이션 검토를 요금제 비교표에서 시작하면 "얼마 내면 되나"라는 질문에 도달한다. 코드에서 시작하면 "무엇을 고쳐야 하나"에 도달한다. 후자가 실제로 답해야 하는 질문이다.

체크리스트로 정리하면 이렇다.

모듈 스코프에 있는 상태(let, Map, 싱글톤)를 전부 찾는다. 각각이 인스턴스 간에 공유되지 않아도 괜찮은지 따져본다.

인프라 설정 파일(프록시, 컨테이너 빌드)에만 존재하는 동작을 찾는다. 보안 헤더, 시스템 패키지, 폰트, 타임존이 여기 숨는다.

감사 문서의 "수용된 리스크"를 다시 읽는다. 수용 근거가 배포 환경에 의존하고 있다면 환경 변경과 함께 무효가 된다.

외부 바이너리에 의존하는 기능을 찾는다. 그 바이너리를 어디서 조달하는지, 새 환경에 그 조달 경로가 있는지 확인한다.

로컬에서 재현되지 않는 환경 차이를 의심한다. 폰트가 대표적이다.

마지막으로 실무적인 주의. 각 요금제의 구체적 수치(실행 시간 상한, 번들 용량, DB 용량, 커넥션 수)는 자주 바뀐다. 이 글의 판단은 내가 검토한 시점의 값을 기준으로 한 것이고, 실제로 옮기기 직전에 공식 문서로 다시 확인하는 게 맞다. 다만 이 글의 결론(요금제는 용량을 사고 아키텍처는 못 산다)은 수치가 바뀌어도 유지된다.