데이터베이스 어댑터
협업 영속성 계약을 이해하고 Memory, SQLite, 사용자 정의 Database Adapter 중에서 선택합니다.
Database Adapter는 Collaboration Service가 사용하는 핵심 협업 데이터를 영속화합니다. 개발자는 IDatabaseAdapter를 구현하여 전체 동작 계약을 충족하는 데이터베이스를 연결할 수 있습니다. Service는 Unit, OT, 리비전의 의미를 관리하고 Adapter는 이를 원자적이며 안전하게 재시도할 수 있는 저장 작업으로 구현합니다.
재시작 후 데이터 유지 확인
Quick Start를 중지하고 루트에서 pnpm example:database-adapter를 실행하세요. http://127.0.0.1:3010/?unit=persistent-sheet&type=2에서 셀을 바꾸고 다른 브라우저에서 확인합니다. 중지 후 같은 명령과 URL로 다시 열어 값이 남아 있는지 확인하세요. 파일은 examples/database-adapter/.data/collaboration.sqlite이며 재시작 시 삭제하지 마세요. 서버는 기존 Unit을 읽고 UNIT_NOT_FOUND일 때만 createUnitFromData()를 호출합니다. server/main.ts를 참고하세요.
Adapter가 저장하는 데이터
핵심 협업 Adapter는 다음을 저장합니다.
- Unit 레코드와 현재 최신 리비전
- 리비전이 지정된 스냅샷
- 확정된 변경 집합
- Sheet 스냅샷에서 참조하는 블록
(unitID, sid, reqId)를 키로 하는 제출 멱등성1 레코드- Unit의 논리 삭제, 복구, 영구 삭제 상태
기본적으로 사용자, 역할, ACL, 디렉터리, 파일, 제품 메타데이터는 저장하지 않습니다. History, Thread Comment, Worktree는 독립적인 Adapter 계약을 사용합니다.
필수 동작 의미
IDatabaseAdapter는 단순한 CRUD 인터페이스가 아닙니다. 구현은 Collaboration Service가 기대하는 정확성 계약을 지켜야 합니다.
| 계약 | 의미 |
|---|---|
| 원자적 생성 | 초기 스냅샷과 Sheet 블록이 함께 보이거나 모두 보이지 않음 |
| 리비전 CAS2 | 최신 리비전이 기대값과 일치할 때만 다음 제출 확정 |
| 제출 멱등성 | 동일한 (unitID, sid, reqId)를 재시도하면 동일한 확정 결과 반환 |
| 연속 변경 집합 | 확정된 변경 집합과 Unit 리비전이 연속성을 유지 |
| 스냅샷 가시성 | 모든 의존 데이터가 저장된 후에만 스냅샷 읽기 가능 |
| 원자적 수명 주기 작업 | 삭제·복구 배치의 모든 Unit이 성공하거나 모두 적용되지 않음 |
| 영구 삭제 표식 | 영구 삭제한 Unit ID는 다시 생성할 수 없음 |
이러한 동시성과 원자성 없이 메서드를 일반 DB 읽기·쓰기에 매핑하면 OT와 복구 동작이 깨집니다.
Memory Adapter
@univerjs-pro/collaboration-database-memory는 프로세스 내부 구현으로 다음에 적합합니다.
- 단위 및 통합 테스트
- 공식 예제
- 임시 개발 환경
- 사용자 정의 Middleware나 클라이언트 구성의 빠른 검증
import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabService } from "@univerjs-pro/collaboration-service";const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });Memory Adapter는 영속 구현과 동일한 리비전, CAS, 멱등성 계약을 따릅니다. 다만 모든 데이터는 현재 Node.js 프로세스에 있으므로 프로세스가 종료되면 사라집니다.
SQLite Adapter
@univerjs-pro/collaboration-database-sqlite는 개발, 테스트, 로컬 애플리케이션, 소규모 사용을 위한 영속 SQLite 구현입니다.
import { mkdir } from "node:fs/promises";import { SQLiteDatabaseAdapter } from "@univerjs-pro/collaboration-database-sqlite";import { UniverCollabService } from "@univerjs-pro/collaboration-service";await mkdir("./data", { recursive: true });const database = new SQLiteDatabaseAdapter({ filename: "./data/collaboration.sqlite", busyTimeoutMs: 5_000,});const service = new UniverCollabService({ dbAdapter: database });SQLite Adapter는 외래 키와 BEGIN IMMEDIATE 쓰기 트랜잭션으로 원자적으로 커밋합니다. 빈 DB에는 현재 스키마를 생성합니다. 불완전하거나 지원하지 않는 스키마는 열지 않으며 알 수 없는 마이그레이션을 자동 적용하지 않습니다.
애플리케이션을 대신해 journal_mode, 파일 위치, 백업 정책, 기타 SQLite 설정을 결정하지 않습니다. SQLite는 개발, 로컬 앱, 소규모 사용을 대상으로 합니다.
사용자 정의 Database Adapter
보통 다음 경우에 적합합니다.
- 이미 PostgreSQL, MySQL 등 업무 DB를 사용함
- 협업 제출을 애플리케이션 고유 DB 메커니즘과 통합해야 함
- 운영 또는 규정 준수 요구가 SQLite의 의도된 범위를 넘음
IDatabaseAdapter를 구현하고 내장 Adapter와 같은 방식으로 Service에 주입합니다.
const database = new ApplicationCollaborationDatabase({ pool });const service = new UniverCollabService({ dbAdapter: database });Service는 특정 DB에 의존하지 않지만 사용자 정의 구현에 트랜잭션, CAS, 중복 제거를 보완해 주지도 못합니다. Adapter가 DB 기능을 사용해 전체 계약을 구현해야 합니다.
Context와 애플리케이션 경계
Service 호출마다 customData가 Middleware, Database Adapter, 관련 Events에 전달되지만 SDK가 자동으로 저장하지는 않습니다. 권한 판단은 보통 Service Middleware에 두고 Adapter 메서드마다 반복하지 않습니다.
Service Middleware → 업무 작업 허용 또는 거부 → Database Adapter → 서버 기준 협업 상태를 원자적으로 저장이렇게 하면 Memory, SQLite, 사용자 정의 Adapter가 같은 애플리케이션 정책을 공유합니다.
업무 데이터와 협업 데이터에 강한 일관성이 필요하면 사용자 정의 Database Adapter가 customData에서 값을 읽고 Adapter의 DB 트랜잭션 안에서 두 데이터를 함께 쓸 수 있습니다.
같은 협업 트랜잭션이 필요하지 않으면 Middleware나 Events로 다른 테이블 또는 외부 시스템에 쓸 수 있습니다. 둘은 기본적으로 Adapter 내부 트랜잭션을 공유하지 않습니다. Events는 협업 커밋 성공 후 발행되는 프로세스 내부의 최선 노력 알림입니다. 필요한 멱등성, 재시도, 보상 처리는 애플리케이션이 담당합니다.
확장 모듈 저장소
History, Thread Comment, Worktree 등은 독립적인 Database Adapter 인터페이스를 정의합니다. 어댑터는 하나의 DB를 공유하거나 별도 저장소를 사용할 수 있습니다. 하나의 사용자 정의 구현이 여러 Adapter 인터페이스를 제공하고 각 Service에 주입될 수도 있습니다.
인터페이스가 독립적이므로 SDK는 기본적으로 모듈 간 트랜잭션을 제공하지 않습니다. 같은 DB를 공유해도 모듈 간 일관성 전략은 애플리케이션이 담당합니다.
Adapter 선택
| Adapter | 적합한 용도 | 주요 제약 |
|---|---|---|
| Memory | 테스트, 예제, 임시 개발 | 프로세스 종료 시 데이터 소멸 |
| SQLite | 개발, 테스트, 로컬 앱, 소규모 사용 | 단일 파일 DB이며 설정은 애플리케이션이 관리 |
| 사용자 정의 | 기존 DB 또는 자체 업무 데이터 통합 | 전체 계약을 직접 구현하고 검증해야 함 |
주입한 Adapter는 모두 애플리케이션이 소유합니다. Service를 먼저 해제한 뒤 Adapter를 해제하세요.
await service.dispose();await database.dispose();실행하며 비교할 수 있는 데이터베이스 어댑터 예제를 참고하세요.
Footnotes
이 가이드는 어떠셨나요?