🗄️
데이터베이스
Neon
Serverless PostgreSQL
서버리스 PostgreSQL 플랫폼으로, Git처럼 데이터베이스를 브랜칭하고, 사용하지 않을 때 자동으로 스케일-투-제로하며, 필요시 자동 확장되는 클라우드 네이티브 PostgreSQL입니다.
Serverless PostgreSQL
서버리스 PostgreSQL 플랫폼으로, Git처럼 데이터베이스를 브랜칭하고, 사용하지 않을 때 자동으로 스케일-투-제로하며, 필요시 자동 확장되는 클라우드 네이티브 PostgreSQL입니다.
Neon은 2021년 PostgreSQL 핵심 개발자들이 설립한 스타트업이 만든 서버리스 PostgreSQL 플랫폼입니다. 기존 AWS RDS나 Cloud SQL과 달리 컴퓨팅과 스토리지가 분리되어 있어 트래픽이 없을 때 0원, 급증하면 자동 확장되는 진정한 서버리스 모델을 제공합니다.
Git 브랜치처럼 DB를 복제해 개발/스테이징 환경을 즉시 생성합니다. Copy-on-Write로 프로덕션 데이터 전체 복사 없이 브랜치 생성 가능.
import { neon } from '@neondatabase/serverless';
// Neon Serverless Driver - HTTP 기반, 웹소켓 불필요
const sql = neon(process.env.DATABASE_URL!);
export const config = { runtime: 'edge' };
export default async function handler(req: Request) {
// 단일 쿼리 (HTTP fetch 기반)
const posts = await sql`
SELECT id, title, created_at
FROM posts
WHERE published = true
ORDER BY created_at DESC
LIMIT 10
`;
// 파라미터화된 쿼리 (SQL Injection 방지)
const userId = 123;
const userPosts = await sql`
SELECT * FROM posts
WHERE author_id = ${userId}
AND published = true
`;
return Response.json({ posts, userPosts });
}
// 트랜잭션이 필요한 경우 (HTTP Transaction)
async function transferCredits(fromId: number, toId: number, amount: number) {
const sql = neon(process.env.DATABASE_URL!, {
fetchOptions: { cache: 'no-store' }
});
// BEGIN/COMMIT/ROLLBACK 대신 단일 요청으로 처리
const result = await sql.transaction([
sql`UPDATE users SET credits = credits - ${amount} WHERE id = ${fromId}`,
sql`UPDATE users SET credits = credits + ${amount} WHERE id = ${toId}`,
sql`INSERT INTO transfers (from_id, to_id, amount) VALUES (${fromId}, ${toId}, ${amount})`,
]);
return result;
}import { Pool } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
import ws from 'ws';
// 웹소켓 설정 (Node.js 환경)
import { neonConfig } from '@neondatabase/serverless';
neonConfig.webSocketConstructor = ws;
// Connection Pool 사용 (기존 pg와 동일한 API)
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// Drizzle ORM과 통합
const db = drizzle(pool);
async function getUsers() {
const client = await pool.connect();
try {
await client.query('BEGIN');
const { rows: users } = await client.query(`
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.author_id
GROUP BY u.id
`);
await client.query('COMMIT');
return users;
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}# Neon CLI 설치
npm install -g neonctl
# 로그인
neonctl auth
# 프로젝트 생성
neonctl projects create --name my-app
# 브랜치 목록 확인
neonctl branches list --project-id
# 프로덕션에서 개발 브랜치 생성 (Copy-on-Write)
neonctl branches create \
--project-id \
--name feature/new-feature \
--parent main
# 특정 시점으로 브랜치 생성 (Point-in-Time)
neonctl branches create \
--project-id \
--name recovery/before-migration \
--parent main \
--at "2024-01-15T10:30:00Z"
# 브랜치 연결 문자열 확인
neonctl connection-string --branch feature/new-feature
# 브랜치 삭제
neonctl branches delete feature/new-feature {
"build": {
"env": {
"DATABASE_URL": "@neon_database_url"
}
}
}name: Create Neon Branch for PR
on:
pull_request:
types: [opened, synchronize]
jobs:
create-branch:
runs-on: ubuntu-latest
steps:
- name: Create Neon Branch
uses: neondatabase/create-branch-action@v4
with:
project_id: ${{ secrets.NEON_PROJECT_ID }}
branch_name: preview/pr-${{ github.event.pull_request.number }}
api_key: ${{ secrets.NEON_API_KEY }}
- name: Run Migrations
run: |
DATABASE_URL=${{ steps.create-branch.outputs.db_url }} \
npx prisma migrate deploy
- name: Comment PR with DB URL
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview DB: \`${{ steps.create-branch.outputs.branch_id }}\``
})-- Neon에서 pgvector 활성화
CREATE EXTENSION IF NOT EXISTS vector;
-- 임베딩 테이블 생성
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- OpenAI ada-002
);
-- HNSW 인덱스 (빠른 유사도 검색)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops);
-- TypeScript에서 사용
import { neon } from '@neondatabase/serverless';
import OpenAI from 'openai';
const sql = neon(process.env.DATABASE_URL!);
const openai = new OpenAI();
async function semanticSearch(query: string) {
// 쿼리 임베딩 생성
const response = await openai.embeddings.create({
model: 'text-embedding-ada-002',
input: query,
});
const embedding = response.data[0].embedding;
// 유사도 검색
const results = await sql`
SELECT id, content,
1 - (embedding <=> ${JSON.stringify(embedding)}::vector) as similarity
FROM documents
WHERE 1 - (embedding <=> ${JSON.stringify(embedding)}::vector) > 0.7
ORDER BY embedding <=> ${JSON.stringify(embedding)}::vector
LIMIT 5
`;
return results;
}Scale-to-Zero 후 첫 연결 시 수 초의 Cold Start가 있습니다. 프로덕션에서는 최소 인스턴스를 1개로 설정하거나 Warm 옵션을 고려하세요.
서버리스 특성상 Connection Pool 크기에 제한이 있습니다. Connection Pooling(PgBouncer 내장)을 활용하고, Serverless Driver 사용을 권장합니다.
브랜치는 Copy-on-Write지만 변경된 데이터는 별도 저장됩니다. 오래된 브랜치나 미사용 브랜치는 정기적으로 삭제하세요.