🗄️ 데이터베이스

SurrealDB

Multi-model Database

Rust로 작성된 멀티모델 클라우드 네이티브 데이터베이스로, Document, Graph, Relational 모델을 단일 플랫폼에서 통합합니다. SurrealQL이라는 직관적인 쿼리 언어와 실시간 구독을 지원합니다.

📖 상세 설명

SurrealDB는 2022년에 공개된 차세대 데이터베이스로, 하나의 DB에서 Document(MongoDB처럼), Graph(Neo4j처럼), Relational(PostgreSQL처럼) 모델을 모두 사용할 수 있습니다. Rust로 작성되어 성능과 안전성이 뛰어나며, 백엔드 없이 프론트엔드에서 직접 DB에 접근하는 아키텍처도 지원합니다.

핵심 특징: 단일 쿼리 언어로 모든 모델

SurrealQL 하나로 관계형 JOIN, 그래프 탐색, 문서 CRUD를 모두 처리. 여러 DB를 조합할 필요 없이 하나로 통합.

지원 데이터 모델

📄
Document
MongoDB처럼 스키마리스 JSON 문서 저장
🕸️
Graph
Neo4j처럼 노드/엣지 관계 탐색
📊
Relational
SQL처럼 테이블 JOIN 쿼리

💻 코드 예제

1. SurrealDB 시작 및 기본 CRUD

SurrealQL
-- SurrealDB 서버 시작 (메모리 모드) -- surreal start memory --user root --pass root -- 네임스페이스와 데이터베이스 선택 USE NS myapp DB production; -- Document 스타일 레코드 생성 (스키마 없이) CREATE user:john SET name = 'John Doe', email = 'john@example.com', age = 30, tags = ['developer', 'rust'], address = { city: 'Seoul', country: 'Korea' }; -- 자동 ID 생성 CREATE user CONTENT { name: 'Jane Smith', email: 'jane@example.com', age: 28 }; -- 조회 (SQL과 유사) SELECT * FROM user WHERE age >= 25; SELECT name, email FROM user WHERE 'developer' IN tags; -- 업데이트 UPDATE user:john SET age = 31, updated_at = time::now(); -- 부분 업데이트 (MERGE) UPDATE user:john MERGE { address: { zip: '06000' } }; -- 삭제 DELETE user:john;

2. 그래프 관계 (Record Links)

SurrealQL
-- 사용자와 게시글 생성 CREATE user:alice SET name = 'Alice'; CREATE user:bob SET name = 'Bob'; CREATE user:charlie SET name = 'Charlie'; CREATE post:1 SET title = 'Getting Started with SurrealDB', author = user:alice, created_at = time::now(); -- 그래프 관계 생성 (팔로우) RELATE user:alice->follows->user:bob SET since = time::now(); RELATE user:alice->follows->user:charlie; RELATE user:bob->follows->user:charlie; -- 그래프 탐색: Alice가 팔로우하는 사람들 SELECT ->follows->user.name FROM user:alice; -- 결과: ['Bob', 'Charlie'] -- 역방향 탐색: Charlie를 팔로우하는 사람들 SELECT <-follows<-user.name FROM user:charlie; -- 결과: ['Alice', 'Bob'] -- 2단계 관계: Alice의 팔로워가 팔로우하는 사람 (친구의 친구) SELECT ->follows->user->follows->user.name FROM user:alice; -- 복잡한 그래프 쿼리: 팔로워 수와 함께 사용자 조회 SELECT name, count(<-follows) AS follower_count, count(->follows) AS following_count FROM user;

3. JavaScript/TypeScript 클라이언트

TypeScript
import Surreal from 'surrealdb.js'; const db = new Surreal(); async function initDB() { // 연결 await db.connect('http://localhost:8000/rpc'); await db.signin({ user: 'root', pass: 'root' }); await db.use({ ns: 'myapp', db: 'production' }); } // CRUD 작업 async function createUser(userData: UserInput) { const [user] = await db.create('user', { name: userData.name, email: userData.email, created_at: new Date(), }); return user; } async function getUser(id: string) { return await db.select(`user:${id}`); } async function searchUsers(query: string) { const users = await db.query(` SELECT * FROM user WHERE name ~ $query OR email ~ $query ORDER BY created_at DESC LIMIT 20 `, { query }); return users[0].result; } // 그래프 쿼리 async function getFollowers(userId: string) { const result = await db.query(` SELECT <-follows<-user.* AS followers FROM user:$userId `, { userId }); return result[0].result[0]?.followers ?? []; } // 실시간 구독 async function subscribeToMessages(roomId: string, onMessage: (msg: Message) => void) { await db.live('message', (data) => { if (data.action === 'CREATE' && data.result.room === `room:${roomId}`) { onMessage(data.result); } }); }

4. 스키마 정의와 권한

SurrealQL
-- 테이블 스키마 정의 DEFINE TABLE user SCHEMAFULL; DEFINE FIELD name ON user TYPE string; DEFINE FIELD email ON user TYPE string ASSERT string::is::email($value); DEFINE FIELD age ON user TYPE int ASSERT $value >= 0 AND $value <= 150; DEFINE FIELD created_at ON user TYPE datetime DEFAULT time::now(); -- 유니크 인덱스 DEFINE INDEX email_idx ON user FIELDS email UNIQUE; -- 전체 텍스트 검색 인덱스 DEFINE INDEX name_search ON user FIELDS name SEARCH ANALYZER ascii BM25; -- 권한 설정 (Row Level Security처럼) DEFINE TABLE post SCHEMAFULL PERMISSIONS FOR select FULL FOR create WHERE $auth.id != NONE FOR update, delete WHERE author = $auth.id; -- 스코프 (인증 컨텍스트) DEFINE SCOPE user_scope SESSION 24h SIGNUP ( CREATE user SET email = $email, password = crypto::argon2::generate($password), name = $name ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password) );

5. 실시간 구독 (Live Queries)

TypeScript
// 실시간 변경 감지 async function setupRealtimeChat(roomId: string) { const db = new Surreal(); await db.connect('ws://localhost:8000/rpc'); await db.use({ ns: 'myapp', db: 'production' }); // LIVE 쿼리로 메시지 구독 const queryUuid = await db.live( 'message', (action, result) => { switch (action) { case 'CREATE': console.log('New message:', result); addMessageToUI(result); break; case 'UPDATE': console.log('Message updated:', result); updateMessageInUI(result); break; case 'DELETE': console.log('Message deleted:', result); removeMessageFromUI(result); break; } } ); // 구독 해제 return () => db.kill(queryUuid); } // 메시지 전송 (다른 클라이언트에 실시간 전파) async function sendMessage(roomId: string, content: string, userId: string) { await db.create('message', { room: `room:${roomId}`, author: `user:${userId}`, content, timestamp: new Date(), }); }

💬 현업 대화 예시

풀스택 개발자
"SurrealDB 쓰면 MongoDB 따로, Neo4j 따로 안 써도 돼. 소셜 기능이랑 문서 저장을 하나의 DB로 해결했어."
백엔드 개발자
"SurrealQL이 SQL이랑 비슷해서 적응하기 쉬웠어. 그래프 쿼리도 -> 화살표로 직관적이고."
시니어 개발자
"아직 초기 단계라 프로덕션에 쓰기엔 좀 고민돼. 클러스터링이나 대용량 처리 사례가 더 쌓여야 할 것 같아."
스타트업 CTO
"MVP에서는 딱 좋아. 백엔드 서버 없이 프론트에서 직접 DB 접근하는 것도 되니까 개발 속도 빨라."

⚠️ 주의사항

프로덕션 사례 부족

2022년 공개된 비교적 새로운 DB입니다. 대규모 프로덕션 사례와 안정성 검증이 아직 부족하므로 중요한 서비스에는 신중한 검토가 필요합니다.

생태계 제한

ORM, 마이그레이션 도구, 모니터링 솔루션 등의 생태계가 PostgreSQL이나 MongoDB에 비해 제한적입니다.

클라이언트 직접 접근 보안

프론트엔드에서 직접 DB 접근 시 권한 설정(PERMISSIONS)을 철저히 해야 합니다. 잘못된 설정은 데이터 유출로 이어질 수 있습니다.

🔗 관련 용어

📚 더 배우기