Blog

Technology-Agnostic

Architectural Playground

Juun

Playground
  1. Blog
  2. Article

Serverless Database Integration

Serverless 의 개념과 Neon 아키텍처, Prisma ORM 을 활용한 framework-agnostic 데이터베이스 패키지 구성까지.

Serverless Database Integration

What is Serverless?

Serverless is a cloud-native development model that allows developers to build and run applications without having to manage servers. The term “serverless” doesn’t mean there are no servers. - Red Hat

Cloud Native 환경이 발전함에 따라 전통적인 구조를 벗어난 새로운 형태의 서비스 또는 어플리케이션들이 많이 등장하고 있다. 마치 서버에 대한 의존도가 사라지는 것 같은 착각을 주는, serverless 도 그 중 하나이다. 도대체 serverless 는 무엇을 말하는 걸까?

Serverless 는 언급했듯, "No Server" 를 의미하는 것이 아니다. Server-maintenance-less 가 의미적으로는 더 정확하다. 서버가 없어지는 게 아니라, 서버에 대한 유지 관리 작업이 없어지는 셈이다. 엄밀하게는, 서버의 유지 관리 작업을 third party vendor 에게 위탁하는 것을 말한다. 기존의 물리 서버, bare metal server 는 24 시간 가동 및 외부 접근 허용이라는 특성 상 보안에 굉장히 취약할 수 밖에 없다. 간단한 NAS (Network-Attached Storage) 를 구축해보려고 해도 각종 보안 설정들 때문에 주저하게 될 정도. 하지만 serverless 는 이런 부분을 떠넘겨버릴 수 있는 것이다(!).

물론 장점만 있는 것은 아니다. 사실상 서버의 통제 권한을 위임하는 것과 다름 없기 때문에, 서버 측의 디버깅이 어려워질 수 있으며, 서버 측 장애 발생 시 즉각적인 대응이 불가능하다는 점, 그리고 직접 서버를 구성하는 것 만큼의 유연성은 확보하기 어렵고, vendor lock-in 이 발생한다. 특히 서비스 운영에 있어서는 서버 장애 대응이 큰 부분을 차지할 수도 있다.

Serverless Computing

24시간 always-on 상태여야 하는 물리 서버와 달리, serverless 는 필요할 때에만 활성화 된다는 on-demand 특징을 갖는다. 또한 Cloud infrastructure 를 기반으로, 그 특징을 살려 auto scaling 과 같은 기능을 함께 제공하는 경우가 많다. 이렇게 server 관리에 더해 cloud 환경 관리까지 겸하는 것을 serverless computing 이라고 하며, 다음과 같이 분류할 수 있다.

  • Function-as-a-Service (FAAS): 단일 함수를 서비스로서 제공하는 것으로, 가장 극단적인 형태의 microservice 가 아닐까 싶다.
  • Backend-as-a-Service (BAAS): 권한처리, 데이터베이스, 메시징, 스토리지 등 backend 서비스 그 자체를 제공한다.
  • Serverless Databases: 인프라 유지 관리를 필요로 하지 않으며 auto scaling 이 가능한 데이터베이스 서비스
  • Serverless Containers: Cloud 환경에 대한 관리가 필요 없는 컨테이너 서비스
  • Serverless Edge Computing: 컴퓨팅을 사용자 또는 리소스에서 물리적으로 가까운 곳에서 진행하여 네트워크 지연을 낮출 수 있는 서비스
  • Serverless GPU: AI/ML 연산을 위한 GPU 자원을 제공하는 서비스

Serverless computing 은 cloud 설정에 대한 복잡성을 신경쓰지 않고도 cloud native 환경을 이용할 수 있다는 점이 다른 무엇보다 매력적이다. 비용적인 측면에서도, 트래픽 변동이 심한 서비스들에 선택적으로 적용하면 큰 이점을 가져다 줄 수 있다. 하지만 Cloud 인프라 장애에 영향을 받는 또다른 어플리케이션이 될 가능성도 무시할 수 없다.

Serverless Database

이 프로젝트의 블로그 시스템은 처음부터 데이터베이스 사용을 염두에 두고 설계됐다. 전까지 사용하고 있었던 file-based dynamic routing 은 인프라를 확정하기 전까지의 임시방편에 불과했다. 사설 서버나 AWS 클라우드 같은 환경에의 서버를 직접 구축할 생각만 하다가, Next.js Dashboard Tutorial 을 진행하면서 Vercel Integrations 에서 제공하는 Neon 데이터베이스를 접하게 됐고, 이를 바탕으로 블로그 시스템을 개편했다. 여기서는 오랜 기간 결정을 내리지 못하고 있던 인프라 문제를 해결해준 Serverless Database, 그 중에서도 Neon 을 중심으로 알아보고자 한다.

Neon

Neon architecture

Serverless database vendor 중 하나인 Neon 은 compute 와 storage 를 분리하고, Neon Control Plane 을 통해 이들의 cloud 자원을 조율하여 Postgres 데이터베이스를 제공한다. Neon Architecture 를 정리하자면 다음과 같다:

  • On-Demand Compute(Postgres): Stateless Query Processor. SQL 쿼리들을 실행하거나 WAL records 를 생성하고, 자주 접근된 data page 의 캐싱을 진행하는 등, 요청받은 작업을 수행하여 Safekeeper 에게 전달한다.
  • Safekeeper: Durability & Replication Mediator. 트랜잭션의 안정성을 보장하며 Pageserver 의 버퍼 또는 relay point 의 역할을 한다. WAL stream 을 compute 로부터 Pageserver 에게 전달하는 일도 맡고 있다.
  • Pageserver: Managed Storage & Caching. multi-tenant storage 의 핵심으로, WAL stream 을 전달 받아 database page 에 적용하고, 데이터 캐시를 최신 상태로 유지하며, Object Storage 로 전달할 데이터를 관리한다. 각 tenant 들의 data isolation 을 담당함과 동시에 git 과 같은 branching 기능도 제공한다.
  • Object Storage(S3): Infinite & Immutable & Durable Archive. 데이터베이스의 이력 (historical data)과 스냅샷, 레이어 파일 등을 저장하는 저장소이다. Pageserver 는 이 곳을 참조해 복구를 진행하지만, 실시간 transaction 커밋에는 관여하지 않는다.

실제 연산 작업이 일어나는 compute 와 데이터를 저장하는 storage 를 분리해내고, compute 를 stateless 상태로 구조화하여 scale-to-zero, 비활성화를 가능하게 함으로써 serverless 서비스로 거듭날 수 있었다.

Integration

Vercel Integrations 에서는 통합 가능한 여러 서비스들을 소개하고 있으며, 이 프로젝트에서는 Neon 을 선택했다. 인프라 및 서버 운영을 위임한다는 부분이 제일 컸고, serverless database 라는 낯선 서비스 형태를 도입하는 것이기 때문에, 다른 대안을 알아보거나 하지는 않았다. Neon 에 Vercel 계정을 연동하여 체험 서비스까지 연결해주는 일련의 과정이 잘 짜여져 있어 따라하기만 해도 쉽게 사용 가능했다. Connection Configuration 에서 확인할 수 있는 PGHOST, PGUSER, PGPASSWORD 로 해당 데이터베이스를 DBeaver 에 직접 연결할 수도 있다. Neon 이 인프라와 데이터베이스를 제공해주지만, 그럼 query 는 어떻게 처리할까?

Prisma ORM

여기서는 튜토리얼에 함께 제공된 Prisma ORM 을 사용했다. 데이터베이스 데이터와 프로그래밍 언어의 객체 형태 사이를 중개해주는 매개체인 ORM, Object-Relational Mapping 중 하나인 Prisma ORM 은 TypeScript 및 Node.js 생태계를 위한 type-safe ORM 이다. 기본적인 사용법은 다음과 같다.

  1. prisma.config.ts 에 연결할 데이터베이스 connection configuration 을 설정한다. (7.x 기준)
  2. Prisma Schema 를 통해 데이터를 직접 모델링하거나, 설계된 모델링을 데이터베이스로부터 받아온다.
  3. prisma generate 를 실행한다. (여기서 prisma 가 객체를 생성해준다.)
  4. Prisma Client 로 생성된 객체를 사용한다.

Framework-Agnostic Database Package

여기에서는 monorepo 구조를 활용해 데이터베이스 패키지를 분리했다. React 나 Next 같은 프레임워크에 대한 dependency 를 완전히 제거하고 데이터베이스 접근 자체를 분리하여 순수한 query 실행의 역할만 할 수 있도록 @juun/db 패키지를 별도로 생성했다.

schema.prisma
1model post {
2  id          Int           @id @default(autoincrement())
3  title       String        @db.VarChar(255)
4  description String?
5  content     String
6  created_at  DateTime      @default(now()) @db.Timestamptz(6)
7  updated_at  DateTime      @updatedAt @db.Timestamptz(6)
8  category    PostCategory?
9  image       String?       @db.VarChar(500)
10  word_count  Int
11  post_tags   post_tag[]
12
13  @@index([id])
14  @@index([category])
15}
16

블로그 포스트를 위한 데이터 모델은 위와 같이 정의했다. post_tag 는 n:n 관계를 처리하기 위해 junction 테이블을 따로 만들어주었다.

client.ts
1import { PrismaPg } from "@prisma/adapter-pg";
2
3import { PrismaClient } from "./generated/prisma/client";
4
5const adapter = new PrismaPg({
6  connectionString: process.env.DATABASE_URL,
7});
8
9const globalForPrisma = globalThis as unknown as {
10  prisma: PrismaClient | undefined;
11};
12
13export const prisma =
14  globalForPrisma.prisma ??
15  new PrismaClient({
16    adapter,
17    log:
18      process.env.NODE_ENV === "development"
19        ? ["query", "error", "warn"]
20        : ["error"],
21  });
22
23if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
24
25export default prisma;
26

데이터베이스의 connection pool 이 불필요하게 많아지는 것을 방지하기 위해서 Singleton 형태로 instance 를 제한한 Prisma Client 를 사용했다.

실제 query 는 entity > method > specific 순서를 따르는 namespace 패턴으로 정리했다. 이 프로젝트에는 GET 외의 method 를 추가할 생각은 없지만, 따로 주석이나 설명을 달지 않고도 의미를 보존하기 위한 게으름의 연장 작업이다.

post.ts
1namespace post {
2  export namespace get {
3  // SELECT (GET) methods
4    export async function all() {
5      const posts = await prisma.post.findMany(...);
6    },
7    ...
8  },
9  ...
10}
11

Closing

Serverless Database 라는 새로운 도메인을 도전적으로 도입해봤다. 이 프로젝트에서는 물리 서버의 유지 관리를 위임할 수 있다는 부분이 가장 크게 작용했고, backend framework 없이도 type-safe 한 데이터베이스 접근이 가능해졌다. 다만 서비스의 특징이 장점이자 동시에 단점일 수 있기 때문에, 실제 도입에서는 고려할 점이 많다.

그 중 하나인 on-demand 의 경우, 인스턴스가 필요에 따라 생성된다는 말은 곧 데이터베이스의 connection 이 없으면 인스턴스가 없어진다는 말과 같다. 즉, connection 이 없는 상태에서는 필요한 모든 인스턴스가 준비될 시간이 추가적으로 필요해지는 셈. 이를 Cold Start 라고 하는데, 이로 인한 지연 시간을 줄이기 위한 가이드도 별도로 존재하니, 성능 면에서 무시할 수 없는 비중을 차지하는 것으로 보인다.

ORM 또한 query 작성에 대한 자유도가 떨어진다는 점에서 복잡한 쿼리가 필요한 서비스에는 적합하지 않을 수 있다. 비용 측면에서도, 이 프로젝트처럼 트래픽이 적은 경우에는 오히려 scale-to-zero 가 비용 절감으로 이어지지만, 꾸준한 트래픽이 발생하는 서비스에서는 직접 운영 비용보다 커질 수도 있다. 도입에는 충분한 고려가 필요하다.

Date2025년 11월 28일
Tags
ORMdatabaseedge computinginfrastructureserverless
Share article
neon architecture
Prisma Logo