RSS듀오랩스
웹 인프라

큐 드라이버를 memory에서 Upstash로 전환하기: 준비와 점검

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

앞선 글에서 백그라운드 작업을 분리하는 코드를 만들었습니다.
드라이버 경계를 세워 두고, 로컬 QStash 개발 서버로 동작까지 확인했습니다.

이제 남은 것은 실제 Upstash 계정을 붙이는 일입니다.
이 글은 그 전환 직전에 무엇을 확인하고, 어떤 값이 왜 필요하며,
전환이 됐는지 어떻게 판별하는가
를 정리한 것입니다.

지금 어떤 상태인지부터 확인합니다

전환 작업에서 제일 먼저 할 일은 "지금 무엇이 돌고 있는가"를 사실로 확인하는 것입니다.
환경변수를 직접 볼 수 없는 경우가 많으니, 동작으로 판별하는 방법을 만들어 두면 편합니다.

제 경우 워커 엔드포인트의 응답 코드가 그 역할을 합니다.

curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST https://example.com/api/jobs -d '{}'
응답 의미
404 QUEUE_DRIVER=memory. 워커가 꺼져 있음
503 upstash로 지정됐지만 서명 키가 없음
401 upstash 전환 완료. 서명 검증이 동작 중

라우트를 이렇게 짜 뒀기 때문입니다.

if (env.queueDriver !== "upstash") {
  return fail("워커가 활성화되지 않았습니다.", 404);
}
if (!env.qstashCurrentSigningKey) {
  return fail("워커가 설정되지 않았습니다.", 503);
}
// 이후 서명 검증 실패 시 401

세 상태를 다른 코드로 갈라 둔 것이 여기서 값을 합니다.
"안 된다"가 아니라 어느 단계에서 멈췄는지를 알려줍니다.

확인해 보니 제 배포는 404, 즉 아직 memory 드라이버였습니다. 예상대로입니다.

같은 방식으로 관리자 토큰 설정 여부도 확인할 수 있습니다.
쓰기 API가 503이면 토큰 미설정, 401이면 설정된 상태입니다.
값을 만들어내지 않는 요청이라 운영 환경에 쏴도 부작용이 없습니다.

전환하면 무엇이 달라지나

코드는 이미 양쪽을 다 지원합니다. 환경변수만 바뀝니다.

memory upstash
처리 시점 enqueue() 가 끝나면 이미 처리됨 몇 초 뒤 워커가 처리
실패 시 로그만 남고 사라짐 QStash가 재시도
요청 제한 인스턴스별 카운터 전역 공유 카운터
POST /api/jobs 404 401 (서명 필요)

여기서 처리 시점의 변화가 가장 중요합니다.
memory에서는 enqueue() 직후에 결과를 읽어도 값이 들어 있습니다.
upstash로 바꾸면 그 코드가 깨집니다.

그래서 저는 처음부터 이 규칙을 문서에 박아 뒀습니다.

enqueue() 직후 결과를 읽지 않는다. 확인이 필요하면 상태 필드를 폴링한다.

전환은 환경변수를 바꾸는 일이지만, 이 규칙을 어긴 코드가 있으면 전환이 곧 장애가 됩니다.
전환 전에 한 번 훑어보시길 권합니다.

필요한 값과 각각의 역할

콘솔에서 받아야 하는 값이 여럿입니다. 그냥 복사해 붙이기보다
각각이 무엇을 하는지 알고 넣는 편이 나중에 디버깅할 때 낫습니다.

큐 (QStash)

변수 역할
QSTASH_TOKEN 우리가 QStash에 작업을 보낼 때 쓰는 인증 정보
QSTASH_CURRENT_SIGNING_KEY QStash가 우리를 호출할 때 붙이는 서명의 검증 키
QSTASH_NEXT_SIGNING_KEY 키 교체(rotation) 대비. 현재 키로 실패하면 이걸로 재시도

토큰과 서명 키의 방향이 반대라는 점이 핵심입니다.
토큰은 나가는 요청용, 서명 키는 들어오는 요청용입니다.
이걸 헷갈리면 "토큰은 맞는데 왜 401이 나지" 같은 상황에 빠집니다.

서명 키가 두 개인 이유는 무중단 키 교체 때문입니다.
키를 바꾸는 순간 이미 큐에 들어가 있던 메시지는 옛 키로 서명돼 있는데,
두 개를 모두 갖고 있으면 그 메시지도 정상 처리됩니다.

요청 제한 (Redis)

변수 역할
UPSTASH_REDIS_REST_URL REST 엔드포인트
UPSTASH_REDIS_REST_TOKEN 인증 토큰

Upstash Redis는 TCP가 아니라 HTTP(REST)로 접근합니다.
서버리스 환경에서 TCP 커넥션을 유지할 수 없기 때문에 나온 설계입니다.
그래서 방화벽이나 커넥션 풀을 신경 쓸 일이 없습니다.

큐만 먼저 켜보고 싶으시면 Redis는 나중에 추가해도 됩니다.
드라이버가 따로 갈리기 때문입니다(QUEUE_DRIVER, RATELIMIT_DRIVER).

그리고 잊기 쉬운 하나

APP_URL=https://example.com

QStash는 우리 앱을 바깥에서 호출합니다. 공개 주소를 모르면 배달할 곳이 없습니다.
enqueue() 할 때 "이 URL로 보내달라"고 알려줘야 하는데, 그 URL이 이 값입니다.

Vercel은 VERCEL_URL 을 자동으로 넣어주지만 배포마다 주소가 바뀝니다.
커스텀 도메인을 쓰신다면 APP_URL 을 명시하는 편이 안전합니다.

콘솔에서 받는 값

https://console.upstash.com 에서 QStash와 Redis를 각각 준비합니다.

공식 문서 기준으로 QStash 토큰은 QStash 탭에서 리전을 고른 뒤 Quickstart 섹션에서
복사합니다. 서명 키도 같은 화면에서 확인할 수 있습니다.
Redis는 데이터베이스를 하나 만들면 REST URL과 토큰이 나옵니다.

콘솔 UI는 종종 바뀌므로, 화면 위치는
공식 문서를 함께 보시는 편이 정확합니다.
이 글에서 값을 갖는 부분은 각 값이 무엇을 하는가이지 클릭 순서가 아닙니다.

Vercel에 넣고 재배포

환경변수를 추가합니다. Production·Preview·Development를 모두 체크합니다.

QUEUE_DRIVER=upstash
RATELIMIT_DRIVER=upstash
APP_URL=https://example.com
QSTASH_TOKEN=...
QSTASH_CURRENT_SIGNING_KEY=...
QSTASH_NEXT_SIGNING_KEY=...
UPSTASH_REDIS_REST_URL=...
UPSTASH_REDIS_REST_TOKEN=...

환경변수만 추가해서는 반영되지 않습니다. 재배포가 필요합니다.
빌드 시점에 결정되는 것은 아니지만, 실행 중인 인스턴스는 기존 환경으로 떠 있기 때문입니다.
Deployments에서 Redeploy 하거나 아무 커밋이나 push하면 됩니다.

이 단계를 잊으면 "환경변수를 넣었는데 그대로네" 하며 시간을 씁니다.

전환됐는지 확인

앞서 만든 판별법을 그대로 씁니다.

# 1. 워커가 켜졌는가
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://example.com/api/jobs -d '{}'
# 404 → 401 로 바뀌면 전환 완료

# 2. 전체 흐름
./scripts/smoke.sh https://example.com

스모크는 전환 전후 모두 통과합니다. 큐 처리 확인 단계를 폴링으로 만들어 뒀기 때문입니다.
차이를 눈으로 보고 싶으면 주문 직후 바로 조회해 보시면 됩니다.
memory에서는 processedAt 이 이미 채워져 있고, upstash에서는 잠깐 null 입니다.

null 이 곧 "요청 경로에서 후처리가 빠졌다"는 증거입니다.

전환 전에 로컬에서 리허설할 수 있습니다

계정을 만들기 전에 똑같은 흐름을 로컬에서 돌려볼 수 있습니다.
QStash는 개발 서버를 제공합니다.

npx @upstash/qstash-cli dev

실행하면 로컬 엔드포인트와 토큰, 서명 키를 출력해 줍니다.
그 값으로 앱을 띄우면 실제 QStash와 같은 방식으로 동작합니다.

저는 이 방식으로 전환 코드를 먼저 검증했습니다.
비동기 처리, 서명 검증 실패 시 401, 공유 카운터 동작까지 전부 로컬에서 확인했습니다.
계정을 붙이는 일이 "되는지 보는 단계"가 아니라 "값만 바꾸는 단계"가 됩니다.

온프레미스는 전환하지 않습니다

같은 코드가 온프레미스에서도 돌지만, 거기는 memory 를 유지합니다.

QStash는 우리 앱을 바깥에서 호출하는 구조라 사설망 주소로는 배달이 안 됩니다.
굳이 쓰려면 워커 엔드포인트를 공개 도메인으로 노출해야 하는데,
얻는 것에 비해 치르는 비용이 큽니다.

한 코드베이스가 환경에 따라 다른 드라이버로 도는 것, 이게 경계를 만든 목적입니다.

QUEUE_DRIVER=memory      # 온프레미스
QUEUE_DRIVER=upstash     # 클라우드

정리

  • 전환 전에 현재 상태를 동작으로 확인합니다. 응답 코드를 상태별로 갈라 두면
    "어느 단계에서 멈췄는지"가 바로 보입니다.
  • 토큰과 서명 키는 방향이 반대입니다. 나가는 요청용과 들어오는 요청용.
  • APP_URL 을 잊지 않습니다. 큐가 우리를 호출하려면 공개 주소를 알아야 합니다.
  • 환경변수 추가 후 재배포해야 반영됩니다.
  • 전환의 실질은 처리 시점의 변화입니다. enqueue() 직후 결과를 읽는 코드가 있으면
    전환이 곧 장애가 됩니다.
  • 계정 없이 로컬에서 리허설할 수 있습니다. 그러면 전환은 값만 바꾸는 일이 됩니다.

관련 글

온프레미스로 돌던 서비스를 Vercel + Supabase 로 옮기고 워커를 분리하기까지,
같은 작업에서 나온 기록입니다.

마지막 수정:

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