이전 글에서는 풀스택 Next.js 애플리케이션에서 Redis를 활용해 사용자 쿼터(quota) 상태를 저장하는 방법을 다뤘습니다. 이를 통해 애플리케이션 성능을 크게 개선할 수 있는데, 그 이유는 일반적으로 Redis가 데이터베이스보다 훨씬 빠르기 때문입니다.
특히 Upstash Redis는 완전 관리형 Redis 데이터베이스이면서 HTTP를 통해 접근할 수 있어 이러한 용도에 최적입니다. 덕분에 Vercel 같은 엣지 런타임(edge runtime)에서 코드를 사용자 가까운 곳에서 실행하며 최소한의 지연 시간으로 서비스할 수 있습니다.
QStash란 무엇인가?
QStash는 서버리스 및 엣지 런타임을 위한 HTTP 기반 메시징 및 스케줄링 솔루션입니다. 다시 말해, HTTP 요청을 전송하는 방식으로 CRON 작업을 실행할 수 있습니다.
CRON 작업은 특정 시간에 태스크를 실행하도록 예약하는 훌륭한 방법입니다. CRON 작업은 원하는 만큼 오래 실행되더라도 애플리케이션 성능이나 최종 사용자 경험에 영향을 주지 않습니다.
이 글에서 CRON 작업의 목표는 사용자 쿼터 상태를 Redis에서 데이터베이스로 동기화하는 것입니다. 이 작업은 매시간 실행되도록 설정합니다.
QStash로 CRON 작업 엔드포인트 만들기
이전 글에서 Redis로 사용자 쿼터 상태를 처리했던 방식을 간단히 복습해 보겠습니다:
// Redis에 저장되는 키. 각 사용자마다 키가 생성되며, ID로 고유하게 구분됩니다.
const quotaKey = `user:${userId}:quota`;
// INCR 명령어를 호출하여 키 값을 1 증가시킵니다.
const quota = await redis.incr(quotaKey);
// 키 값이 1이라면 해당 키가 새로 생성된 것입니다.
// EXPIRE 명령어를 사용해 24시간 만료 기간을 설정합니다.
if (quota === 1) {
await redis.expire(quotaKey, 60 * 60 * 24);
}
이 코드를 바탕으로 QStash를 사용한 CRON 작업 엔드포인트를 만들 수 있습니다. CRON 작업 엔드포인트는 /api/cron/update-usage URL에서 트리거됩니다.
먼저 Next.js 엔드포인트를 위한 새 파일을 생성합니다:
touch pages/api/cron/update-usage.ts
다음으로, 이 엔드포인트는 오직 QStash만 트리거할 수 있도록 해야 합니다. 이 역할을 @upstash/qstash가 대신 처리해 줍니다. 이렇게 하지 않으면 누구나 CRON 작업 엔드포인트를 호출할 수 있어 보안 문제가 발생할 가능성이 있습니다.
@upstash/qstash를 설치합니다:
npm install @upstash/qstash
이제 CRON 작업 엔드포인트의 코드에 집중할 차례입니다. 우리가 할 작업을 간단히 정리하면 다음과 같습니다:
SCAN명령어(고성능 커서 기반 페이지네이션)를 사용해 Redis에서 모든 사용자 쿼터 키를 가져옵니다.SCAN으로 찾은 각 키에 대해 사용자 ID와 쿼터를 담은 객체 배열users를 생성합니다.users배열의 각 사용자마다 트랜잭션을 생성해 데이터베이스의 쿼터를 업데이트합니다. 이때 Prisma를 사용합니다.@upstash/qstash/nextjs의verifySignature함수로 엔드포인트 함수를 내보내 QStash만 엔드포인트를 트리거할 수 있도록 합니다.
전체 엔드포인트 코드는 아래와 같습니다. 코드의 모든 부분에 주석을 달아 두었으니 참고하세요:
// /pages/api/cron/update-usage.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { verifySignature } from "@upstash/qstash/nextjs";
import prisma from "@/lib/prisma";
// 환경 변수를 사용해 Upstash Redis 인스턴스를 생성합니다.
// .env 파일에 해당 값들이 정의되어 있어야 합니다.
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// `@upstash/qstash`에서 요구하는 설정입니다.
export const config = { api: { bodyParser: false } };
// Redis 데이터베이스에서 Prisma 데이터베이스로 데이터를 옮겨
// 모든 사용자의 사용량을 업데이트합니다.
async function handler(req: NextApiRequest, res: NextApiResponse) {
// `SCAN` 중 발견된 모든 키를 담습니다.
const keys: string[] = [];
// 커서의 현재 위치로, 매번 `SCAN` 후 업데이트됩니다.
let cursor = 0;
// `SCAN` 명령어로 커서 기반 페이지네이션을 실행합니다.
// 커서가 페이지네이션 끝에 도달하면 Redis는 `0`을 반환합니다.
do {
const [nextCursor, newKeys] = await redis.scan(cursor, {
// 찾고자 하는 키 패턴을 지정합니다. `*`는 와일드카드입니다.
match: "user:*:quota",
// `SCAN` 명령어가 반환할 키의 개수를 제한합니다.
count: 10,
});
cursor = nextCursor;
keys.push(...newKeys);
} while (cursor !== 0);
// 사용자 ID와 쿼터를 담는 객체 배열입니다.
const users: { id: number; quota: number }[] = [];
// 찾은 키들을 기반으로 Redis에서 각 사용자의 사용량을 가져옵니다.
for (const key of keys) {
// 키는 `user:${id}:quota` 형식입니다. 키를 분할해 ID를 추출합니다.
const id = key.split(":")[1];
// `GET` 명령어로 사용자의 현재 쿼터를 조회합니다.
const quota = await redis.get(`user:${id}:quota`);
users.push({ id: parseInt(id, 10), quota });
}
// 각 사용자마다 트랜잭션을 생성해 데이터베이스의 쿼터를 업데이트합니다.
const promises = users.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { quota: user.quota },
}),
);
// 모든 트랜잭션을 실행합니다.
await Promise.all(promises);
return res.status(200).json({ message: "OK" });
}
// `verifySignature` 함수로 핸들러를 내보내 QStash만
// 인증을 거쳐 CRON 작업을 트리거할 수 있도록 합니다.
export default verifySignature(handler);
가장 어려운 부분은 끝났습니다!
QStash 설정하기
이제 코드를 배포하기 전에 QStash를 설정해야 합니다. QStash는 넉넉한 무료 플랜을 제공하며, 하루 최대 500건의 요청까지 사용할 수 있습니다.
Upstash Console에 로그인한 뒤 QStash 탭을 클릭합니다.
그런 다음 Request Builder를 사용해 새로운 예약 CRON 작업을 생성합니다. 직관적인 UI 덕분에 매우 간단합니다:

Schedule 버튼을 클릭한 후 아래로 스크롤하면 Scheduled Jobs 섹션에서 생성한 CRON 작업을 확인할 수 있습니다:

마지막 단계로, Next.js 애플리케이션에 환경 변수 2개를 설정해야 합니다. 이 변수들은 Next.js 애플리케이션이 호스팅되는 곳, 여기서는 Vercel에 추가합니다.
Request Builder 섹션에는 복사할 준비가 된 환경 변수 2개가 있습니다:
QSTASH_CURRENT_SIGNING_KEYQSTASH_NEXT_SIGNING_KEY
이 키들은 수신되는 메시지를 인증하는 데 반드시 필요합니다. 설정하지 않으면 누구나 여러분의 엔드포인트를 호출할 수 있게 됩니다.

환경 변수를 확보했다면 Vercel 프로젝트에 추가합니다.
Vercel 프로젝트로 이동해 Settings를 클릭한 후 Environment Variables를 선택합니다:

환경 변수 2개를 추가했다면 이제 코드를 배포할 수 있습니다.
QStash가 주기적으로 CRON 작업 엔드포인트를 트리거하여 매시간 모든 사용자의 사용량을 자동으로 업데이트합니다.
마무리
Upstash는 서버리스 환경을 위한 다양한 도구를 제공합니다. QStash와 Redis를 함께 활용하면 다양한 용도에 맞춰 성능이 뛰어나고 확장 가능한 애플리케이션을 구축할 수 있습니다.
실제 구현 사례가 궁금하다면, 스프레드시트를 API로 변환해 주는 도구인 fastsheet에서 동일한 시스템이 적용되어 있으니 확인해 보세요.