Vercel의 백엔드는 그냥 다른 백엔드와 같은 줄 알았어요...
Source: Dev.to
Vercel과 전통적인 백엔드에 대한 교훈
프로젝트를 통해 Next.js를 깊이 있게 배우는 과정에서, 저는 Next.js 백엔드 서버를 일반적인 Go 백엔드(의존성 주입)처럼 설계하려고 시도했습니다. 하지만 이는 불가능하다는 것을 알게 되었습니다. Vercel의 /api 라우트를 “미니 Express 서버”처럼 취급하는 것은 근본적인 아키텍처 오류입니다. 두 경우 모두 Node.js를 실행하지만, 그 기반 인프라(서버리스 vs. 장기 실행) 때문에 완전히 다른 규칙이 적용됩니다.
비슷한 방식으로 동작하는 다른 서비스로는 Netlify, Cloudflare Pages / Workers, 그리고 AWS Amplify가 있습니다.
1. Process Lifecycle: Ephemeral vs. Persistent
Traditional Backend (Long‑Running)
- Concept: 프로세스가 시작되면 수동으로 중지하거나 충돌할 때까지 계속 살아 있습니다.
- Global root: 영구적; 메모리는 한 번 할당되며 수천 개의 요청에 걸쳐 계속 사용할 수 있습니다.
- Implication: 서버가 내부 RAM에 있는 정보를 며칠, 몇 주 동안 “기억”한다고 믿을 수 있습니다.
Vercel (Serverless)
- Concept: 프로세스는 일시적입니다. 요청이 들어오면 생성되고 응답이 전송되는 순간(또는 그 순간에) 종료(또는 동결)됩니다.
- Global root: 자주 파괴됩니다.
- Implication: 모든 내부 상태가 사라집니다. 모든 요청을 서버가 콜드 스타트를 수행한 것처럼 처리하십시오.
2. State Management: The Global Variable Trap
Traditional Backend (Long‑Running)
- Memory: 전역 변수는 공유됩니다. User A가 전역 카운터를 업데이트하면, 같은 프로세스를 이용하는 User B는 업데이트된 값을 보게 됩니다.
- Garbage collection: 프로세스가 살아 있는 한, GC는 전역 변수를 reachable(도달 가능)하다고 판단해 무시하므로, 메모리 내 캐시나 간단한 카운터를 사용할 수 있습니다.
Vercel (Serverless)
- Memory: 전역 변수는 격리됩니다. 10명의 사용자가 API를 호출하면 Vercel은 10개의 별도 실행 환경(인스턴스)을 띄울 수 있습니다.
- Trap: User A가 인스턴스 #1에서 카운터를 업데이트합니다. User B가 인스턴스 #2에 접근하면, 해당 인스턴스는 자체 전역 루트를 가지고 있어 카운터 값이 여전히 0입니다.
- Note: 워밍된 인스턴스는 연속적인 요청 사이에 전역 변수를 유지할 수 있지만, 유휴 상태가 되면 콜드 상태로 전환되어 모든 전역 변수가 사라집니다.
- Solution: 영속성을 위해 메모리 내 데이터를 의존하지 마세요. Redis나 PostgreSQL과 같은 외부 스토어를 사용하십시오.
3. Concurrency: Thread Pooling vs. Horizontal Scaling
Traditional Backend (Long‑Running)
- Mechanism: 하나의 서버가 단일 스레드와 이벤트 루프(Node.js)를 사용해 다수의 동시 요청을 처리하고, 연결 풀을 관리합니다.
- Scaling: 트래픽이 많아지면 서버가 더 열심히 작업하게 되며, 과부하가 걸리면 요청이 대기열에 쌓입니다.
Vercel (Serverless)
- Mechanism: Vercel은 multiplication 방식으로 확장합니다—트래픽 급증 시 함수의 복사본을 다수 생성합니다.
- Challenge: 1,000개의 함수가 동시에 시작되면 각각 새로운 데이터베이스 연결을 열어 연결 고갈 위험이 발생하고 DB가 다운될 수 있습니다.
- Solution: 연결 풀러를 사용합니다(예: Prisma Accelerate 또는 Supabase pooling).
4. 실행 타이밍: 동기 vs. 백그라운드 작업
전통적인 백엔드 (장기 실행)
res.send(); fireAndForgetEmail();와 같이 응답을 보낸 뒤에도 백그라운드에서 코드를 계속 실행할 수 있습니다.- 프로세스가 살아 있기 때문에 백그라운드 작업이 끝날 때까지 진행됩니다.
Vercel (서버리스)
- 흐름:
res.send()가 호출되는 순간 런타임 환경이 일시 중지되거나 종료됩니다. - 재앙: 응답 이후에 시작된 백그라운드 작업은 실행 도중 중단될 수 있어, 작업이 불완전하거나 누락될 수 있습니다 (예: 절반만 전송된 이메일).
- 해결책: 응답하기 전에 모든 작업을 완료하거나, Upstash Workflow 와 같은 큐 서비스에 장기 실행 작업을 위임합니다.
5. Persistent Connections: WebSockets vs. HTTP
Traditional Backend (Long‑Running)
- Connectivity: 상태 저장 연결을 지원합니다. 클라이언트는 실시간 채팅이나 라이브 업데이트를 위해 TCP 소켓을 열어둘 수 있습니다(WebSockets); 서버 프로세스는 상대편을 유지할 수 있도록 계속 실행됩니다.
Vercel (Serverless)
- Connectivity: 무상태 HTTP 요청만 지원합니다. 함수는 응답 후 종료되므로 연결을 유지할 수 없습니다.
- Result:
socket.io와 같은 것을 사용하면 각 요청 후 “서버”가 사라지기 때문에 실패합니다. - Solution: Pusher 또는 Ably와 같은 써드파티 실시간‑as‑a‑service 솔루션을 사용하세요.
이 문서는 AI 도움을 받아 개인 메모로 작성되었습니다.