Neon Postgres 완벽 가이드 — 서버리스 PostgreSQL
이 글의 핵심
Neon은 PostgreSQL 호환의 서버리스 데이터베이스로, 스토리지와 컴퓨트 분리·브랜치·프리뷰·연결 풀링에 강합니다. 이 글에서는 아키텍처부터 Vercel·Next.js·ORM 연동, 운영 시 고려사항까지 한 번에 다룹니다.
이 글의 핵심
Neon은 클라우드에서 제공하는 PostgreSQL 호환 관리형 데이터베이스입니다. 전통적인 “한 대의 장기 실행 인스턴스” 모델과 달리 스토리지 계층과 컴퓨트 계층을 분리하고, 브랜치(branch) 로 개발·프리뷰 환경을 빠르게 만들며, 서버리스 런타임(Vercel, AWS Lambda 등)과 맞추기 위한 연결 풀링·전용 드라이버를 제공합니다.
이 글에서는 다음을 순서대로 다룹니다.
- Neon의 핵심 개념과 아키텍처(왜 서버리스에 적합한지)
- 브랜칭과 프리뷰(Preview) 환경 운영 패턴
- 커넥션 풀링과
@neondatabase/serverless드라이버의 역할 - 오토스케일과 슬립(Suspend) 으로 인한 지연·운영 이슈
- Drizzle ORM, Prisma 와의 통합
- Vercel, Next.js 배포 시 환경 변수·연결 문자열 구성
- 실전 프로젝트를 가정한 스키마·인덱스·멀티테넌시 설계 요령
참고: 과금·한도·리전·정확한 제품명은 시기에 따라 바뀔 수 있으므로, 운영 전 Neon 공식 문서와 콘솔을 반드시 확인하시기 바랍니다.
1. Neon의 핵심 개념
1-1. PostgreSQL 호환과 “관리형”의 의미
Neon은 애플리케이션 관점에서는 표준 PostgreSQL 프로토콜로 접속합니다. 즉, 대부분의 SQL, 마이그레이션 도구, ORM은 기존 Postgres 습관을 그대로 가져올 수 있습니다. 다만 인프라 내부는 일반적인 단일 VM 위 Postgres 설치와 동일하지 않으며, 스토리지 서브시스템과 컴퓨트가 분리되어 있습니다.
관리형으로서 사용자는 OS 패치, 디스크 증설, 스탠바이 구성 같은 작업에서 벗어나 연결 문자열·브랜치·역할·백업 정책에 집중할 수 있습니다.
1-2. 스토리지와 컴퓨트 분리
Neon을 이해하는 열쇠는 스토리지와 컴퓨트(실행 중인 Postgres 프로세스) 를 느슨하게 결합한다는 점입니다.
- 스토리지: 데이터 페이지가 지속적으로 유지되는 계층으로 이해할 수 있습니다(내부적으로는 WAL·페이지 서버 등으로 구성).
- 컴퓨트: 실제로 쿼리를 실행하는 Postgres 인스턴스입니다. 트래픽에 따라 크기를 조정하거나, 유휴 시 중단될 수 있습니다.
이 분리 덕분에 브랜치를 만들 때 전체 데이터를 물리적으로 복사하는 대신 스냅샷·분기에 가까운 방식으로 환경을 나눌 수 있고, 개발자 경험 측면에서 프리뷰 DB를 빠르게 붙이기 좋습니다.
1-3. 서버리스 워크로드와의 궁합
서버리스 함수(예: Vercel Functions)는 실행 시간이 짧고, 동시에 많은 인스턴스가 뜰 수 있습니다. 이때 DB 쪽에서는 동시 연결 수가 급증할 수 있고, 각 함수 프로세스가 장기 TCP 풀을 유지하기 어렵습니다. Neon은 이런 패턴을 전제로 풀러(Pooler) 와 HTTP/WebSocket 기반 서버리스 드라이버를 제공하여, 애플리케이션 코드가 “연결 폭증”에 덜 노출되도록 돕습니다.
2. 아키텍처를 한 장으로 이해하기
아래는 개념적 흐름입니다(세부 구현은 제품 업데이트에 따라 달라질 수 있습니다).
flowchart LR
subgraph app [애플리케이션]
Next[Next.js / API]
Edge[Edge Runtime 선택 시]
end
subgraph neon [Neon]
Pooler[연결 풀러]
Compute[Postgres 컴퓨트]
Storage[분리 스토리지 / WAL]
end
Next --> Pooler
Edge --> Pooler
Pooler --> Compute
Compute --> Storage
- 애플리케이션은 가능하면 풀러 엔드포인트를 통해 연결해 연결 수를 안정화합니다.
- 컴퓨트는 부하에 따라 스케일되고, 유휴 시 슬립할 수 있습니다.
- 스토리지는 컴퓨트와 독립적으로 지속성을 담당합니다.
운영 관점에서는 “내가 RDS에서 하던 그대로”가 아니라 연결·콜드 스타트·풀러 URL을 함께 설계해야 한다는 점이 가장 큰 차이입니다.
3. 브랜칭(Branching)과 프리뷰 환경
3-1. 브랜치가 해결하는 문제
팀이 커밋·PR 단위로 기능을 나누듯, 데이터베이스도 환경별로 스키마와 데이터가 갈라지면 통합 비용이 커집니다. Neon 브랜치는 메인(프로덕션에 해당하는 브랜치) 을 기준으로 개발·스테이징·프리뷰용 컴퓨트(및 필요한 스토리지 분기)를 제공하여, “로컬 덤프 복원”이나 “공용 스테이징 덮어쓰기”에 덜 의존하게 합니다.
3-2. 프리뷰(Preview) 환경에 연결하는 일반적 패턴
Vercel 등에서 PR마다 배포 URL이 생기듯, PR마다 Neon 브랜치를 만들고 해당 브랜치의 연결 문자열을 프리뷰에 주입하는 패턴이 많습니다.
- CI/CD에서 Neon API 또는 CLI로 브랜치 생성 → 마이그레이션 적용 → 시드(선택) → 환경 변수 출력
- 프리뷰 앱은 읽기 전용 API 위주인지, 쓰기가 필요한지에 따라 시드 규모와 보존 기간을 정책화합니다.
3-3. 운영 시 주의할 점
- PII(개인정보) 가 포함된 프로덕션을 그대로 분기하면 프리뷰 URL이 유출될 때 위험이 커집니다. 가능하면 익명화·축소된 시드를 쓰거나, 접근을 IP·Basic Auth 등으로 제한합니다.
- 브랜치마다 마이그레이션 버전이 어긋나면 디버깅이 어려워지므로, 단일 마이그레이션 소스(Prisma migrate, Drizzle kit 등)를 PR과 함께 고정합니다.
4. 연결 풀링(Connection Pooling)과 서버리스 드라이버
4-1. 왜 풀링이 필요한가
PostgreSQL은 동시 연결에 한계가 있으며, 서버리스는 연결 생성 비용이 반복됩니다. 풀러는 많은 짧은 클라이언트 연결을 적은 수의 서버 측 연결로 묶어 줍니다. Neon 콘솔에서 제공하는 Pooler 호스트(예: 리전 풀러 엔드포인트)를 사용하는 것이 일반적입니다.
4-2. @neondatabase/serverless 드라이버
Node·엣지 런타임에서 TCP 제약이 있거나, 서버리스 친화적 연결이 필요할 때 Neon 서버리스 드라이버를 사용합니다. 내부적으로는 런타임에 맞는 방식으로 쿼리를 전달합니다(구현 세부는 공식 패키지 문서를 따릅니다).
아래는 개념 예시입니다. 실제 프로젝트에서는 콘솔에서 발급한 호스트·사용자·DB명·SSL을 반영합니다.
// 개념 예시: @neondatabase/serverless로 쿼리 실행
import { neon } from '@neondatabase/serverless';
// DATABASE_URL은 풀러를 가리키는 것이 일반적입니다.
const sql = neon(process.env.DATABASE_URL!);
export async function getUserCount() {
const rows = await sql`SELECT count(*)::int AS c FROM users`;
return rows[0]?.c ?? 0;
}
이 코드는 템플릿 리터럴 형태로 파라미터 바인딩을 유도하여 SQL 삽입을 줄이는 패턴을 보여 줍니다. 문자열 연결로 쿼리를 만들면 안 됩니다.
왜 이런 API가 쓰이나요? 서버리스 환경에서는 커넥션 객체를 글로벌에 오래 붙잡아 두기 어렵고, 드라이버가 연결 수명을 다루기 쉽게 추상화하는 경우가 많습니다.
언제 주의하나요? 트랜잭션이 길거나, 대량 배치를 한 연결에 묶어야 할 때는 풀러·서버리스 드라이버의 트랜잭션 모델을 문서와 함께 확인해야 합니다. 배치는 별도 워커·상시 서비스로 분리하는 설계가 더 안전한 경우가 많습니다.
4-3. 일반 pg 클라이언트와의 선택
장기 실행 서버(컨테이너 한 개가 계속 뜨는 구조)에서는 pg + 적절한 풀 크기로 충분한 경우가 많습니다. 반면 Vercel 함수처럼 인스턴스가 잦게 바뀌면 Neon 권장 조합(풀러 + 서버리스 드라이버 또는 ORM 어댑터) 을 우선 검토합니다.
5. 오토스케일(Autoscaling)과 슬립(Sleep)
5-1. 오토스케일
Neon 컴퓨트는 부하에 따라 자원을 늘리거나 줄이는 방향으로 동작합니다(플랜·설정에 따름). 애플리케이션은 “내가 고정 크기 인스턴스를 샀다”가 아니라 성능이 단계적으로 변할 수 있다고 가정하는 편이 안전합니다.
5-2. 슬립과 콜드 스타트
유휴 상태가 길면 컴퓨트가 중단(suspend) 될 수 있고, 이후 첫 요청에서 깨어나는 지연이 발생할 수 있습니다. 사용자에게 체감되는 “첫 로딩이 느리다”는 현상으로 나타날 수 있습니다.
완화 아이디어
- 워밍업 요청(스케줄된 ping) — 비용·정책과 트레이드오프
- 중요 경로는 캐시·엣지·읽기 복제(지원 시)와 조합
- 슬립이 치명적인 관리 도구·배치는 상시 소량 컴퓨트 또는 별도 스케줄을 검토
5-3. SLA와 운영 기대치
무료·저가 플랜은 슬립·연결 제한이 체감될 수 있습니다. 프로덕션에서는 플랜·리전·가용성을 요구사항에 맞게 선택하고, 헬스체크·에러율을 모니터링합니다.
6. Drizzle ORM 통합
Drizzle은 스키마를 코드로 두고 타입 안전 쿼리를 지향합니다. Neon과 함께 쓸 때는 드라이버 계층만 Postgres에 맞게 연결하면 됩니다.
6-1. 패키지와 연결
일반적으로 @neondatabase/serverless 와 drizzle-orm 을 조합합니다. 프로젝트에 따라 drizzle-orm/neon-http 등 어댑터 경로를 선택합니다(버전별 import는 공식 예제를 따릅니다).
// 개념 예시: Drizzle + Neon serverless (import 경로는 사용 중인 drizzle 버전 기준)
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);
이 구성이 의미하는 것은, Drizzle이 SQL 빌더/매퍼 역할을 하고 실제 전송은 Neon 서버리스 클라이언트가 담당한다는 점입니다.
6-2. 마이그레이션
drizzle-kit 으로 스키마 드리프트를 관리합니다. 브랜치마다 drizzle migrate 를 CI에서 실행할지, 로컬에서만 적용할지 팀 규칙을 정해야 합니다.
주의: 프리뷰 브랜치에 실수로 프로덕션과 다른 마이그레이션 순서가 적용되면, 나중에 머지할 때 충돌이 납니다. 단일 소스의 마이그레이션 히스토리를 유지합니다.
7. Prisma 통합
Prisma는 스키마.prisma 와 마이그레이션, 클라이언트 생성이 일체형입니다. Neon과 함께 쓸 때 흔한 포인트는 다음과 같습니다.
7-1. 연결 문자열과 풀러
DATABASE_URL 은 Neon 콘솔의 Pooler 연결을 사용하는 경우가 많습니다. Prisma 문서에서는 서버리스 환경을 위해 드라이버 어댑터 또는 연결 제한 파라미터를 안내하기도 합니다(Prisma·Neon 공동 문서를 참고).
7-2. 서버리스에서의 Prisma 클라이언트 생성
함수가 호출될 때마다 new PrismaClient() 를 무분별하게 생성하면 연결 폭증이 날 수 있습니다. Next.js에서는 개발 모드 핫 리로드와 맞물려 중복 생성 문제가 생기기도 하므로, 전역 캐시 패턴을 쓰는 예제가 널리 알려져 있습니다.
// Next.js App Router 등에서 흔히 쓰는 전역 Prisma 싱글톤 패턴(개념)
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
왜 이렇게 하나요? 개발 환경에서 모듈이 여러 번 로드될 때 클라이언트가 중복되는 것을 막고, 프로덕션에서는 인스턴스 재사용을 돕기 위함입니다.
Neon과 함께할 때는 Prisma 버전에 맞는 Neon 어댑터(또는 권장 datasource 설정) 를 적용하고, Edge Runtime 사용 여부에 따라 지원 드라이버가 달라질 수 있으므로 공식 호환표를 확인합니다.
8. Vercel·Next.js 통합
8-1. 환경 변수
Vercel 프로젝트 설정에 다음을 둡니다.
DATABASE_URL: Neon Pooler URL(권장되는 경우가 많음)DATABASE_URL_UNPOOLED(이름은 팀 컨벤션): 마이그레이션·일회성 관리 작업용 직접 연결이 필요할 때 구분
마이그레이션 도구는 장시간 연결이나 여러 명령을 연속 실행하는 경우가 있어 풀러가 부적합할 수 있습니다. 이때 unpooled URL을 쓰는 패턴이 흔합니다.
8-2. Next.js App Router에서의 위치
- 서버 컴포넌트·Route Handler에서 DB 접근 시 런타임이 Node인지 Edge인지에 따라 사용 가능한 드라이버가 달라집니다.
- Edge에서는 TCP 기반
pg가 제한될 수 있어 Neon 서버리스 드라이버 쪽이 맞는 경우가 많습니다.
8-3. 배포 파이프라인
- GitHub에 푸시 → Vercel 빌드
- 빌드 시 마이그레이션을 실행할지, 런타임 전에 별도 잡으로 할지 결정
- 프리뷰 환경에 별도 Neon 브랜치를 붙일 경우, Vercel 환경 변수 스코프(Production / Preview)로 URL 분리
9. 실전 프로젝트 데이터베이스 설계 예시
아래는 SaaS 스타일의 멀티테넌트 앱을 가정한 최소 예시입니다. 실제 도메인에 맞게 테이블을 늘리되, 테넌트 격리와 인덱스를 먼저 고정하는 편이 안전합니다.
9-1. 요구사항 가정
- 조직(
tenant) 단위로 데이터가 분리된다. - 사용자는 여러 조직에 속할 수 있다(멤버십).
- 감사를 위해
created_at을 남긴다.
9-2. 스키마 스케치(SQL)
-- 예시: 멀티테넌트 최소 모델
CREATE TABLE tenants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email citext UNIQUE NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE memberships (
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role text NOT NULL CHECK (role IN ('owner', 'member')),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, user_id)
);
CREATE INDEX memberships_user_idx ON memberships (user_id);
설계 의도
- 기본 키는
uuid로 충돌 위험을 줄입니다(다만 인덱스 크기 트레이드오프는 있습니다). - 멤버십은 복합 기본 키로 중복 소속을 방지합니다.
ON DELETE CASCADE는 “조직 삭제 시 하위 데이터 정리”를 DB에 위임합니다. 비즈니스가 소프트 삭제를 요구하면 이 부분은 조정합니다.
9-3. 쿼리 패턴과 인덱스
자주 쓰는 조회가 WHERE tenant_id = ? AND ... 형태라면 tenant_id 선두 컬럼을 인덱스에 포함합니다. Neon’s Postgres이므로 EXPLAIN (ANALYZE, BUFFERS) 로 실제 플랜을 확인하는 습관이 중요합니다.
9-4. 확장 시나리오
- 대량 로그·이벤트는 메인 OLTP와 분리해 아카이브 테이블 또는 별도 스토어를 검토합니다.
- 검색이 복잡해지면 PostgreSQL Full Text 또는 전용 검색 엔진을 병행합니다.
10. 보안·비밀 관리
- 연결 문자열은 Vercel Secret 으로만 보관하고, 저장소에 커밋하지 않습니다.
- 프리뷰 브랜치에 프로덕션 덤프를 넣을 때는 최소 권한 역할과 네트워크 제한을 검토합니다.
- 애플리케이션 DB 사용자는 DDL 권한을 제한하고, 마이그레이션 전용 역할을 분리하는 편이 안전합니다.
11. 요약
| 주제 | 실무에서 기억할 점 |
|---|---|
| 아키텍처 | 스토리지·컴퓨트 분리로 브랜치·스케일·슬립이 가능 |
| 브랜치 | PR·환경별 DB를 빠르게 만들되 PII·마이그레이션 일관성 관리 |
| 연결 | 서버리스는 풀러 + 서버리스 드라이버/ORM 조합을 우선 검토 |
| 슬립 | 첫 요청 지연 가능 → 캐시·플랜·워밍업 등으로 완화 |
| ORM | Drizzle·Prisma 모두 가능, 런타임(Edge/Node) 과 마이그레이션 URL 주의 |
| Next·Vercel | 환경 변수 스코프와 프리뷰 브랜치를 함께 설계 |
Neon은 “PostgreSQL을 쓰되, 서버리스 배포 모델에 맞게 연결·환경·브랜치를 재구성해야 한다”는 점이 핵심입니다. 이 글의 패턴을 바탕으로 팀 규칙(마이그레이션·시드·프리뷰 데이터)만 합의하면 프로덕션 도입 논의가 훨씬 수월해집니다.
참고
배포 전에는 git add · git commit · git push 후 npm run deploy 절차를 따르시기 바랍니다.