이전 블로그 포스트에서는 캐싱의 장점과 일반적인 캐싱 기법, 그리고 다양한 코드 예제를 통해 Upstash Redis를 활용해 SQL 데이터를 캐싱하는 방법을 살펴보았습니다.
이번에는 한 단계 더 나아가 실제 적용 사례를 확인해 보겠습니다. 널리 사용되는 ORM인 Prisma를 예로 들어, 데이터베이스 쿼리에 Upstash Redis 캐싱을 어떻게 적용하는지 알아보겠습니다.
이 글에서는 Prisma와 Upstash Redis를 통합하여 cache-aside(캐시 어사이드) 기법으로 쿼리를 캐싱하고, 데이터베이스 부하를 줄이며, 애플리케이션 성능을 향상시키는 방법을 다룹니다.
Prisma란 무엇인가?
Prisma를 설정하기 전에 몇 가지 기본 개념과 Prisma의 역할부터 짚고 넘어가겠습니다.
Prisma는 애플리케이션과 데이터베이스 사이의 인터페이스를 제공하는 객체 관계 매핑(Object Relational Mapper, ORM) 도구입니다.
ORM은 개발자가 원시 SQL 쿼리를 직접 작성하는 대신, 객체 지향적인 고수준 코드로 데이터베이스와 상호작용할 수 있게 해주는 프로그래밍 기법입니다. 쉽게 말해, 관계형 데이터베이스와 객체 지향 프로그래밍 언어를 연결하는 다리 역할을 합니다.
ORM은 데이터베이스 테이블을 프로그래밍 언어의 클래스 또는 모델에 매핑합니다. 테이블의 각 행(row)은 클래스의 인스턴스에 해당합니다. 이는 객체 지향 언어로 작성된 애플리케이션에서 수행하는 데이터 모델링의 기본입니다. ORM은 이 과정을 대신 처리해 주며, SQL 쿼리를 직접 작성하는 대신 메서드를 제공합니다. 개발자는 객체나 클래스의 메서드를 호출하여 CRUD(생성, 조회, 수정, 삭제) 작업을 수행할 수 있습니다.
예를 들어, 원시 SQL로 users 테이블을 조회하는 쿼리는 다음과 같습니다:
SELECT * FROM users WHERE id = 1;
ORM을 사용하면 동일한 쿼리가 다음과 같이 표현됩니다:
const user = await user.findUnique({ where: { id: 1 } });
즉, ORM은 데이터베이스와 상호작용하는 데 필요한 작업을 대신 수행하고, 조회된 데이터를 실행 중인 프로그램에서 바로 사용할 수 있도록 만들어 개발자에게 큰 편의를 제공합니다.
Prisma는 소프트웨어 애플리케이션에서 가장 널리 사용되고 사용하기 쉬운 ORM 중 하나입니다.
Prisma가 좋은 선택인 이유를 몇 가지 살펴보겠습니다:
타입 세이프(Type-Safe) 쿼리: Prisma는 데이터베이스 스키마를 기반으로 TypeScript 클라이언트를 자동 생성하므로, 런타임 오류가 아닌 개발 단계에서 오류를 잡아낼 수 있습니다.
간편한 데이터 모델링:
schema.prisma파일에서 선언적 문법으로 데이터베이스 구조를 정의하면, Prisma가 이를 필요한 SQL 또는 API 호출로 변환해 줍니다. 이 파일에 모델을 작성해 테이블을 생성하는 예제도 곧 살펴보겠습니다.다양한 데이터베이스 지원: Prisma는 PostgreSQL, MySQL, SQLite, MongoDB 등 인기 있는 데이터베이스와 함께 작동합니다. 지원되는 데이터베이스 목록은 Prisma 공식 문서에서 확인할 수 있습니다.
마이그레이션 관리: Prisma는 데이터베이스 스키마와 코드베이스를 동기화 상태로 유지할 수 있도록 사용하기 쉬운 마이그레이션 시스템을 제공합니다.
또한 Prisma는 CLI와 Studio(웹 기반 데이터베이스 GUI)를 제공하여 데이터베이스 관리를 더욱 쉽게 만들어 줍니다.
Prisma 설정하기
이제 Prisma를 설치하고 애플리케이션에서 어떻게 작동하는지 살펴보겠습니다.
이 포스트의 핵심은 Prisma 쿼리 캐싱이므로, 로컬 환경에서 간단한 SQLite 데이터베이스를 사용해 Prisma의 동작 방식을 시연해 보겠습니다.
먼저 애플리케이션이 SQLite 데이터베이스에 연결하고 상호작용할 수 있도록 sqlite3를 설치해야 합니다. 앞서 언급했듯이 SQLite 설정은 Prisma가 데이터베이스와 어떻게 작동하는지 보여주기 위한 것일 뿐입니다. 다른 데이터베이스를 선호한다면 Prisma 공식 문서에서 해당 데이터베이스와의 통합 방법을 찾아볼 수 있습니다.
터미널에서 프로젝트 디렉터리로 이동한 후 SQLite를 설치합니다.
npm install sqlite3
다음으로 Prisma ORM을 사용하기 위해 Prisma CLI를 설치합니다.
npm install prisma --save-dev
그런 다음 Prisma를 초기화합니다. 초기화를 진행하면 prisma 디렉터리와 그 안에 schema.prisma 파일이 생성됩니다. schema.prisma 파일은 데이터베이스 구조(모델, 필드 등)를 정의하는 곳입니다.
npx prisma init
이제 Prisma 스키마를 정의해 보겠습니다. 에디터에서 prisma/schema.prisma 파일을 열어 보세요.
이 파일은 세 개의 블록으로 구성됩니다. 첫 번째는 generator 블록으로, Node.js 애플리케이션에서 사용할 클라이언트를 생성하도록 Prisma를 구성합니다. 두 번째는 datasource 블록으로, Prisma가 연결할 데이터베이스 정보를 입력합니다. 마지막 블록에는 데이터베이스 모델들이 들어가며, 여기서 테이블과 필드(데이터 타입 및 제약 조건 포함)를 정의할 수 있습니다.
아래 예제를 살펴보겠습니다.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model user {
id Int @id @default(autoincrement())
name String
email String @unique
age Int?
}
위와 같이 데이터베이스 타입을 명시하고 URL을 지정합니다. 참고로 프로젝트에 DATABASE_URL 환경 변수를 설정하는 것을 잊지 마세요. 이 예제에서는 file:./dev.db로 설정하면 됩니다. 다른 데이터베이스 타입에 연결하는 방법은 Prisma 공식 문서를 참고하세요.
또한 컬럼의 타입과 제약 조건을 정의하여 user 테이블을 선언했습니다. 필요에 따라 다양한 데이터 타입을 활용해 더 복잡한 모델을 만들 수 있지만, 이 데모에서는 기본 설정으로 충분합니다.
필요한 테이블을 생성해 스키마를 데이터베이스와 동기화하려면 마이그레이션 명령을 실행해야 합니다. --name 플래그로 마이그레이션 이름을 지정하면 스키마 변경 이력을 추적하기가 더 쉬워집니다.
npx prisma migrate dev --name init
이제 Prisma ORM과 함께 사용할 SQLite 데이터베이스가 준비되었습니다.
Upstash Redis 설정하기
이 섹션은 가장 빠르게 완료할 수 있는 부분입니다. 이미 여러 번 해보신 분들도 계시겠지만, 처음부터 끝까지 완전한 가이드가 되도록 단계를 간략히 짚고 넘어가겠습니다. 이미 완료한 단계가 있다면 필요한 부분으로 바로 건너뛰셔도 됩니다.
Upstash 콘솔을 통해 Redis 데이터베이스를 생성합니다.
"Create database" 버튼을 클릭하고 팝업되는 모달의 단계를 완료하면 Redis 데이터베이스가 생성됩니다.
데이터베이스 리전을 선택할 때는 Upstash Redis 데이터베이스의 읽기 복제본(read replica) 옵션을 잊지 말고 확인하세요. 글로벌 Redis의 장점은 이전 블로그 포스트에서 자세히 다루었습니다.

Redis 데이터베이스가 준비되었습니다. Upstash Redis 설정의 마지막 단계는 애플리케이션에서 Redis 클라이언트를 초기화하는 것입니다. 이를 위해 먼저 Upstash Redis SDK를 설치해야 합니다.
npm install @upstash/redis
그다음 Upstash 콘솔에서 Redis 엔드포인트와 비밀번호(token)를 복사한 후, Upstash Redis에 연결할 코드에서 Redis 클라이언트를 구성합니다.
Upstash Redis로 Prisma 쿼리 캐싱하기
앞서 언급했듯이 우리는 cache-aside 전략을 사용합니다. 이 방식에서는 먼저 요청된 데이터가 캐시에 존재하는지 확인합니다. 존재한다면 Upstash Redis에서 데이터를 가져와 반환하고, 존재하지 않는다면 앞서 생성한 Prisma 클라이언트로 SQL 데이터베이스를 조회한 뒤, 조회 결과로 캐시를 채웁니다(populate).
import { Redis } from '@upstash/redis';
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const redis = new Redis({
url: '<UPSTASH-REDIS-ENDPOINT>',
token: '<UPSTASH-REDIS-TOKEN>',
})
async function getUser(userId: string) {
const cacheKey = `user:${userId}`;
// 캐시 확인
const cachedUser = await redis.get(cacheKey);
if (cachedUser) {
console.log("Cache hit");
return JSON.parse(cachedUser);
}
// 데이터베이스 조회
console.log("Cache miss");
const user = await prisma.user.findUnique({
where: { id: userId },
});
// 캐시에 저장
if (user) {
await redis.set(cacheKey, JSON.stringify(user), { ex: 3600 }); // 1시간 동안 캐싱
}
return user;
}
이 예제에서 볼 수 있듯이, prisma.<tableName>.findUnique() 메서드에 조회 조건을 정의하는 파라미터를 전달하면 코드 형태로 SQL 데이터베이스를 조회할 수 있습니다.
참고로, findMany() 함수를 사용하면 user 테이블의 모든 사용자를 조회할 수도 있습니다:
const user = await prisma.user.findMany();
마무리
이번 포스트에서는 Prisma 쿼리를 캐싱하기 위해 Prisma와 Upstash Redis를 통합하는 방법을 알아보았습니다.
Prisma와 Upstash Redis는 모두 직관적이고 사용하기 쉬울 뿐만 아니라, 개발자 친화적인 아키텍처를 갖추면서도 상당한 효과를 발휘합니다. Prisma는 데이터베이스 관리를 코드 형태로 가능하게 하는 매핑 기능 덕분에 개발 시간을 크게 절약해 줍니다. 또한 Upstash Redis로 Prisma 쿼리를 캐싱하면 데이터 조회 지연 시간(latency)을 크게 줄이고, 애플리케이션의 메인 SQL 데이터베이스에 가해지는 부하도 경감할 수 있습니다.
이 글이 여러분께 도움이 되기를 바랍니다!