Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Nuxt 3와 서버리스 Redis 시작하기 – Upstash로 페이지 방문자 추적 앱 만들기

소개

애플리케이션 사용량을 추적하거나, 리소스 사용을 제한하거나, 캐시에서 데이터를 가져와 앱 성능을 높여야 하는 앱을 만들어 본 적이 있다면 그 해답으로 Redis를 떠올렸을 것입니다. Redis는 인메모리(in-memory) 키-값(key-value) 데이터베이스로, 오픈 소스이며 'Remote Dictionary Server'의 약자입니다.

이 글에서는 Upstash와 Redis 데이터베이스, 그리고 Vue SSR 프레임워크인 Nuxt 3 베타 릴리스를 다룹니다. Redis 데이터베이스를 처음 접하는 분들을 위한 입문 가이드로, Nuxt 앱의 페이지 방문 수를 추적하는 간단한 앱을 함께 만들어 보겠습니다.

참고 자료

  • GitHub 저장소: https://github.com/Krutie/upstash-nuxt-demo
  • 데모
    • Cloudflare Worker: https://upstash-demo.krutie-patel.workers.dev/contact
    • Netlify: https://thirsty-visvesvaraya-a09ab9.netlify.app/

Upstash란 무엇인가?

Upstash는 Redis 데이터베이스에 서버리스(serverless) 방식으로 접근할 수 있는 서비스입니다. 그렇기 때문에 Redis의 기본 개념, 즉 Redis의 활용 사례와 다양한 데이터 타입을 다루는 명령어들을 먼저 이해하는 것이 중요합니다.

Redis란 무엇인가?

Redis는 다음과 같은 용도로 널리 사용됩니다.

  • 데이터 및 세션 캐싱
  • 리더보드(Leaderboard) – 게임이나 게임화(gamification) 원칙으로 만들어진 소프트웨어에서 이름과 점수 순위를 매길 때
  • 큐(Queue) – 작업을 나중에 백그라운드에서 처리하도록 예약할 때
  • 사용량 계량/카운팅 – 리소스 사용 제한, 리소스 배분 제어 또는 전자상거래 사이트, 소셜 미디어, 모바일 앱 등에서 대규모 사용 현황을 모니터링하고 분석할 때
  • 콘텐츠 필터링 – 예를 들어 금지어 목록과 대조해 콘텐츠를 필터링할 때

기본적으로 Redis는 데이터를 키-값 쌍으로 저장하지만, 리스트(List), 셋(Set), 정렬된 셋(Sorted Set) 같은 고급 자료 구조로도 데이터를 저장할 수 있습니다. Redis는 이러한 자료 구조를 조작하는 명령어 세트도 제공합니다. 예제 앱에서 그중 일부를 사용할 예정이니, 주요 자료 구조의 개요를 살펴보겠습니다.

  • Lists – 기본 배열과 유사합니다. 시퀀스 양쪽 끝에서 아이템을 push/pop 하고, 개별 아이템을 조회하는 등 다양한 연산을 수행할 수 있습니다. 리스트 명령어는 L로 시작합니다(LPOP, LPUSH, LSET 등).
  • Hash – 하나의 Redis 키 안에 여러 키-값 쌍 그룹을 저장할 수 있습니다. 해시 명령어는 H로 시작합니다(HSET, HGET, HDEL 등).
  • Sets – 리스트와 비슷하지만, 셋은 값이 고유하고 순서가 없습니다. 따라서 정렬은 불가능하지만 아이템 추가, 삭제, 포함 여부 확인이 매우 빠릅니다. 셋 명령어는 S로 시작합니다(SADD, SCARD, SISMEMBER 등).
  • Sorted sets – 셋과 유사하지만, 키-값 쌍처럼 보이는 스코어(score) 기준으로 정렬할 수 있습니다. 이 숫자 스코어를 조작하고 정렬할 수도 있습니다. 정렬된 셋 명령어는 Z로 시작합니다(ZADD, ZINCRBY, ZSCORE 등).

다른 Redis 명령어에 대해 더 알아보려면 https://redis.io/commands 를 참고하세요.

Upstash 설정

계정 및 데이터베이스 설정 방법은 공식 문서 https://docs.upstash.com/ 을 참고하세요.

Nuxt 앱을 만들기 전에 Upstash 계정이 준비되어 있는지 확인하세요. 무료 플랜(free-tier)으로 하나의 데이터베이스를 생성할 수 있습니다.

데이터베이스가 생성되면 어떤 Redis 클라이언트든 사용해 Redis 데이터베이스를 생성하고 접근할 수 있습니다. 또는 Upstash 콘솔에 내장된 👇 브라우저 기반 CLI를 사용하면 즉시 시작할 수 있습니다.

Nuxt 3와 서버리스 Redis 시작하기 – Upstash로 페이지 방문자 추적 앱 만들기

Upstash 콘솔에서 제공하는 브라우저 기반 CLI

Redis-cli

로컬 터미널에 redis-cli를 설치하면 명령줄 인터페이스에서 바로 Redis 데이터베이스를 생성하고 접근할 수 있습니다.

Redis npm 패키지

Redis 데이터베이스와 상호작용하는 npm 패키지도 여러 가지가 있습니다. 이 글에서는 Nuxt 프로젝트에서 Redis 명령어에 접근하기 위해 1) @upstash/redis와 2) ioredis 두 가지를 사용합니다.

다음 섹션에서는 Nuxt 프로젝트를 설정하겠습니다. Nuxt는 Vue 기반의 SSR 프레임워크로, Nuxt Labs가 최근 Nuxt 3 베타를 발표했습니다. 새로운 Nuxt 3 프로젝트를 설정해 보겠습니다.

Nuxt 3는 현재 베타 단계이므로 아직 프로덕션 환경에 사용할 준비가 되지 않았다는 점을 유의하세요.

⚡ Nuxt 3 살펴보기

Nuxt 3는 Nuxt 앱을 생성하는 완전히 새로운 CLI인 nuxi를 도입했습니다.

npx nuxi init nuxt3-app

이제 /pages 디렉터리를 만들고 아래 👇처럼 몇 개의 간단한 라우트를 추가합니다.

pages/
├── index.vue
└── about.vue
app.vue

app.vue는 Nuxt 3에 새롭게 도입된 메인 컴포넌트입니다. /pages 디렉터리에 정의된 모든 라우트에 대해 app.vue가 로드됩니다.

Nitro 서버 엔진 ⚙️

Nuxt 3는 Nitro라는 완전히 새로운 서버 엔진도 도입했습니다. Nitro의 강력한 기능을 활용하면 server 디렉터리 아래에 apimiddleware 하위 디렉터리를 만드는 것만으로 서버 API 엔드포인트서버 미들웨어를 손쉽게 만들 수 있습니다. GitHub 저장소에서 최소화된 디렉터리 구조를 확인해 보세요.

server/
├── api
└── middleware

API와 미들웨어는 모두 API 요청을 처리하고 Promise 또는 JSON 데이터를 반환하는 기본 함수(default function)를 export 해야 합니다. Nuxt 2와 달리 nuxt.config.js에서 서버 미들웨어를 따로 정의할 필요가 없습니다.

앱을 만들기 위해 필요한 전체적인 단계는 다음과 같습니다.

  • 먼저 Redis 데이터베이스에 연결합니다.
  • 페이지 방문을 기록하기 위해 서버로 들어오는 모든 페이지 요청을 가로채고, 해당 페이지의 카운터를 1씩 증가시킨 후 그 값을 Redis 데이터베이스에 저장합니다.
  • 그다음 클라이언트 측에서 API를 호출해 Redis 데이터베이스에서 방문 수를 가져와 Nuxt 페이지에 표시합니다.

개념적으로 아래 다이어그램은 우리가 만들려는 구조를 보여줍니다 👇

Nuxt 3와 서버리스 Redis 시작하기 – Upstash로 페이지 방문자 추적 앱 만들기

Nuxt 3에 대해 더 알아보려면 https://v3.nuxtjs.org/getting-started/installation 을 참고하세요.

REST 기반 Redis 클라이언트 사용하기

Upstash는 자체 HTTP/REST 기반 Redis 클라이언트인 @upstash/redis를 제공하며, 이를 Nuxt 프로젝트의 의존성으로 추가할 수 있습니다.

yarn add @upstash/redis

Redis DB 인증하기

Redis 데이터베이스를 인증하려면 다음 환경 변수가 필요합니다.

  • REST URL (UPSTASH_REDIS_REST_URL)
  • Token (UPSTASH_REDIS_REST_TOKEN)

이 두 값은 Upstash 콘솔의 해당 데이터베이스 페이지에서 찾을 수 있습니다.

비공개 런타임 설정(Private Runtime Config)

이제 이 환경 변수들을 서버 측에서 노출하기 위해 Nuxt가 제공하는 런타임 설정(runtime config)을 nuxt.config.js 파일에 정의합니다.

// nuxt.config.js
export default defineNuxtConfig({
  publicRuntimeConfig: {},

  privateRuntimeConfig: {
    UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
    UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
  },
});

이후에는 #config를 import 하여 이 환경 변수들에 직접 접근할 수 있습니다.

import { Redis } from "@upstash/redis";
import config from "#config";

const redis = new Redis({
  url: config.UPSTASH_REDIS_REST_URL,
  token: config.UPSTASH_REDIS_REST_TOKEN,
});

대안으로 제로 설정(zero-config) 방식도 있습니다. UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN.env 파일에 직접 넣고, 변수를 Redis에 전달할 필요 없이 const redis = Redis.fromEnv()로 Redis 인스턴스를 생성하는 것입니다. 단, 이 마법 같은 기능은 @upstash/redis 클라이언트를 사용할 때만 동작합니다.

Nuxt 서버 미들웨어로 요청 가로채기

이제부터는 필요한 어떤 Redis 명령어든 자유롭게 사용할 수 있습니다. 이 예제에서는 정렬된 셋(sorted set)을 사용합니다. 정렬된 셋은 값이 고유하고, SCORE를 정렬하고 조작할 수 있기 때문입니다.

예를 들어 zincrby를 사용하면 페이지 요청이 발생할 때마다 해당 페이지의 SCORE를 증가시킬 수 있습니다.

// server/middleware/pageCount.js
import { Redis } from "@upstash/redis";
import { getRedisKey } from "../utils";

const redis = Redis.fromEnv();

export default async function (req, res, next) {
  const redisKey = getRedisKey(req.url);
  await redis.zincrby("myPageCounts", 1, redisKey);
  next();
}

네임스페이스 생성하기

Redis는 데이터를 키-값 쌍으로 저장하는 NoSQL 데이터베이스입니다. 자동 증가 키(auto-incrementing key)나 UUID 같은 고유 식별자를 동적으로 생성하는 개념이 없습니다. 바로 이 지점에서 getRedisKey() 유틸리티 함수가 필요합니다.

이 유틸리티 함수는 요청 URL을 처리해 고유한 키를 생성하고, 우리는 이 키를 사용해 페이지별 방문 수를 저장합니다. 이렇게 하면 동일한 키에 대해 카운터가 중복 증가하는 것을 막을 수 있습니다.

이 예제에서는 요청 URL을 받아 '/''.'으로 모두 치환하여 고유한 네임스페이스 키를 생성합니다.

export const getRedisKey = (url: string) => {
  const reqURL = url?.replace("/", ".");
  const redisKey = reqURL === "." ? "page.home" : `page${reqURL}`;
  return redisKey;
};

예를 들어 /about 페이지가 page.about이라는 네임스페이스 키로 변환됩니다.

REST API 엔드포인트로 Redis DB 접근하기

이제 각 페이지의 현재 카운트, 즉 SCORE를 조회하는 API 엔드포인트를 만들어 보겠습니다.

Nuxt 3에서 데이터를 가져오는 방법은 두 가지가 있습니다. 1) useAsyncData와 2) useFetch입니다. app.vue에서는 ohmyfetch 라이브러리가 제공하는 $fetch와 함께 useAsyncData를 사용하겠습니다.

// app.vue
<script setup>
  const router = useRoute();
  const { data: count } = await useAsyncData('Count', () => $fetch('/api/count', { params: { path: router.path }}))
</script>

위 코드에서 볼 수 있듯이 API 호출 시 접근 중인 페이지를 식별하기 위해 👇 라우터 경로를 쿼리 파라미터로 함께 전달합니다.

$fetch("/api/count", { params: { path: router.path } });

서버 미들웨어와 달리 이 API 엔드포인트인 /api/count를 호출해서 방문 수를 가져와야 합니다. 이제 재미있는 부분입니다!

server/api/count.ts 위치에 이 API 엔드포인트를 만들겠습니다. 클라이언트에서 전달된 쿼리 파라미터에 접근하기 위해 h3 라이브러리가 제공하는 useQuery 메서드를 활용합니다.

// server/api/count.ts
import { useQuery } from "h3";

export default async (req, res) => {
  let query = await useQuery(req);
  const redisKey = getRedisKey(query.path);
};

👆 여기서도 동일한 getRedisKey() 유틸리티를 사용해, 이 키가 미들웨어에서 페이지 카운트를 증가시킬 때 사용한 네임스페이스 키와 일치하도록 합니다.

이제 데이터베이스에 확실히 존재하는 이 키를 zscore에 전달해 SCORE(방문 수)를 가져올 수 있습니다 👇

// server/api/count.ts
// ...
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

export default async () => {
  // ...
  const count = await redis.zscore("myPageCounts", redisKey);
  return { count };
};

ioredis로 Redis API 직접 사용하기

같은 작업을 ioredis 라이브러리로도 구현할 수 있습니다.

yarn add ioredis

ioredis를 사용하면 auth 메서드를 사용할 수 없습니다. 대신 Upstash 콘솔에서 제공되는 연결 문자열(connection string)을 사용해 Redis 데이터베이스에 연결할 수 있습니다.

앞서 rest-url과 token을 설정했던 것처럼, UPSTASH_REDIS_CONN을 런타임 설정 변수로 지정합니다.

// nuxt.config.js
export default defineNuxtConfig({
  publicRuntimeConfig: {},

  privateRuntimeConfig: {
    UPSTASH_REDIS_CONN: process.env.UPSTASH_REDIS_CONN,
  },
});

그다음 미들웨어에서 new Redis() 인스턴스를 생성해 연결을 만들고, 👇 client를 통해 모든 Redis 명령어에 접근합니다.

// server/middleware/pageCount.js

import config from "#config";
import Redis from "ioredis";

const client = new Redis(config.UPSTASH_REDIS_CONN);

Nuxt 서버 미들웨어로 요청 가로채기

Nuxt 미들웨어는 이전과 동일하게 유지되며, zincrby를 호출하는 부분만 👇 아래와 같이 변경됩니다.

// server/middleware/pageCount.js
// ...
export default async function (req, res, next) {
  // ...
  await client.zincrby("myPageCounts", 1, redisKey);
  next();
}

REST API 엔드포인트로 Redis DB 접근하기

앞서 카운트를 조회하기 위해 커스텀 API 엔드포인트 server/api/count.ts를 만들었습니다. 이 엔드포인트 역시 동일하게 유지되며, 👇 zscore 메서드를 호출하는 부분만 약간 변경됩니다.

// server/api/count.ts
import config from "#config";
import Redis from "ioredis";

const client = new Redis(config.UPSTASH_REDIS_CONN);

export default async (req: IncomingMessage, res: ServerResponse) => {
  // ...
  const count = await client.zscore("myPageCounts", redisKey);
  return { count };
};

Upstash CLI로 테스트하기

모든 데이터를 정렬된 셋에 저장했으므로, zrange를 사용해 정렬된 셋의 모든 아이템을 가져올 수 있습니다.

Upstash 콘솔에서 제공되는 Redis CLI에 접속해 다음 명령어를 실행해 보세요.

zrange myPageCounts 0 -1

👆 여기서:

  • myPageCounts는 우리가 만든 정렬된 셋의 이름입니다.
  • 0 -1은 범위를 의미하며, 0은 시작 값, -1은 셋의 마지막 아이템을 나타냅니다.

위 명령어는 SCORE 없이 모든 키를 나열합니다. WITHSCORES 옵션을 추가하면 👇 이 문제를 해결할 수 있습니다.

// 낮은 SCORE부터 조회
zrange myPageCounts 0 -1 WITHSCORES

// 높은 SCORE부터 조회
zrevrange myPageCounts 0 -1 WITHSCORES

// page.home 히트 수의 SCORE 조회
zscore myPageCounts page.home

API와 미들웨어를 테스트하는 동안 웹사이트에서 일어나는 모든 활동을 Upstash 콘솔에서 확인해 보세요.

Nuxt 3와 서버리스 Redis 시작하기 – Upstash로 페이지 방문자 추적 앱 만들기

Upstash 콘솔에서 확인하는 리소스 사용량

Nuxt 3 앱 배포하기

Nuxt 3 앱을 배포할 때는 Nitro 서버 엔진이 중요한 역할을 합니다.

Netlify

Netlify에 배포하려면 평소처럼 Nuxt 앱의 GitHub 저장소를 연결하면 됩니다. 배포가 원활하게 진행되도록 다음 세 가지 항목을 꼭 확인하세요.

  • Netlify에서 빌드 명령어는 npm run build, 게시(publish) 디렉터리는 dist로 설정합니다.
  • 배포 전에 Netlify에서 rest-url, token 또는 Redis 연결 문자열에 대한 환경 변수를 생성해 둡니다.
  • 마지막으로 Nuxt 프로젝트 루트에 netlify.toml 파일을 만들었는지 확인합니다. 파일 내용은 netlify.toml 예제를 참고하세요.

yarn build를 실행하면 Nuxt 3는 .output이라는 디렉터리를 생성하며, 우리는 .toml 파일에서 이 경로를 함수(functions) 경로로 지정했습니다.

// netlify.toml
// ...
[build]
// ...
functions = ".output/server"

이 글의 Nuxt 예제가 Netlify에서 실행되는 모습은 여기서 확인할 수 있습니다: https://thirsty-visvesvaraya-a09ab9.netlify.app/

Cloudflare

Nuxt 3 앱은 터미널에서 바로 Cloudflare Worker에 배포할 수도 있습니다! Nuxt 3 공식 문서는 로컬에서 앱을 테스트할 때 Miniflare를, 미리보기 및 게시에는 Wrangler를 사용할 것을 권장합니다.

프로젝트 루트에 Cloudflare account_id와 환경 변수를 포함한 wrangler.toml 파일을 추가해야 합니다. 예제 파일을 참고하세요.

Cloudflare 배포 시에는 entry-point를 지정하고, 빌드 명령어에 NITRO_PRESET=cloudflare를 설정합니다.

// wrangler.toml
// ..
[site]
bucket = ".output/public"
entry-point = ".output"

[build]
command = "NITRO_PRESET=cloudflare yarn nuxt build"
upload.format = "service-worker"

이 글의 Nuxt 예제가 Cloudflare Worker에서 실행되는 모습은 여기서 확인할 수 있습니다: https://upstash-demo.krutie-patel.workers.dev/contact

마무리

이 글에서는 Upstash 서비스를 활용한 Redis의 가장 기본적인 활용법을 다루며, Nuxt(v3) 같은 SSR 프레임워크와 통합하는 방법을 살펴보았습니다.

  • Redis 데이터베이스에 연결하고,
  • 고유한 키를 생성해 키-값 쌍을 데이터베이스에 기록하며,
  • Redis 데이터베이스에서 데이터를 읽고,
  • 앱을 Netlify와 Cloudflare Workers에 배포하는 과정까지 진행했습니다.

Upstash는 생성한 각 데이터베이스에 대해 데이터 트래픽 암호화 옵션과, 여러 가용 영역(availability zone)에 걸친 데이터 복제 옵션, 그리고 REST API 응답을 전 세계에 분산된 엣지(edge) 위치에서 캐싱하는 옵션도 제공합니다. 이 옵션들은 Upstash 콘솔의 데이터베이스 details(세부 정보) 탭에서 찾을 수 있습니다.

Redis 데이터베이스를 처음 배우려는 초보자에게 Upstash는 이미 익숙하고 사랑받는 프론트엔드 기술과 함께 쉽게 시작할 수 있는 훌륭한 선택입니다. 이 글이 여러분의 Redis 여정에 좋은 출발점이 되기를 바랍니다.