🗄️
데이터베이스
SurrealDB
Multi-model Database
Rust로 작성된 멀티모델 클라우드 네이티브 데이터베이스로, Document, Graph, Relational 모델을 단일 플랫폼에서 통합합니다. SurrealQL이라는 직관적인 쿼리 언어와 실시간 구독을 지원합니다.
Multi-model Database
Rust로 작성된 멀티모델 클라우드 네이티브 데이터베이스로, Document, Graph, Relational 모델을 단일 플랫폼에서 통합합니다. SurrealQL이라는 직관적인 쿼리 언어와 실시간 구독을 지원합니다.
SurrealDB는 2022년에 공개된 차세대 데이터베이스로, 하나의 DB에서 Document(MongoDB처럼), Graph(Neo4j처럼), Relational(PostgreSQL처럼) 모델을 모두 사용할 수 있습니다. Rust로 작성되어 성능과 안전성이 뛰어나며, 백엔드 없이 프론트엔드에서 직접 DB에 접근하는 아키텍처도 지원합니다.
SurrealQL 하나로 관계형 JOIN, 그래프 탐색, 문서 CRUD를 모두 처리. 여러 DB를 조합할 필요 없이 하나로 통합.
-- 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;-- 사용자와 게시글 생성
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;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);
}
});
}-- 테이블 스키마 정의
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)
);// 실시간 변경 감지
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(),
});
}2022년 공개된 비교적 새로운 DB입니다. 대규모 프로덕션 사례와 안정성 검증이 아직 부족하므로 중요한 서비스에는 신중한 검토가 필요합니다.
ORM, 마이그레이션 도구, 모니터링 솔루션 등의 생태계가 PostgreSQL이나 MongoDB에 비해 제한적입니다.
프론트엔드에서 직접 DB 접근 시 권한 설정(PERMISSIONS)을 철저히 해야 합니다. 잘못된 설정은 데이터 유출로 이어질 수 있습니다.