데이터베이스 어댑터

협업 영속성 계약을 이해하고 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나 클라이언트 구성의 빠른 검증
TypeScript
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 구현입니다.

TypeScript
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에 주입합니다.

TypeScript
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 메서드마다 반복하지 않습니다.

text
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를 해제하세요.

TypeScript
await service.dispose();await database.dispose();

실행하며 비교할 수 있는 데이터베이스 어댑터 예제를 참고하세요.

Footnotes

  1. 멱등성은 같은 제출을 재시도해도 편집이 두 번 적용되지 않도록 합니다. 재시도 시 제출 식별자를 유지해야 하며, 같은 내용이라도 새 식별자를 쓰면 새 제출로 처리됩니다. ↩

  2. CAS는 현재 값이 예상 값과 일치할 때만 갱신합니다. 현재 리비전 확인과 다음 상태 기록을 원자적으로 수행하여 동시 제출이 같은 리비전을 차지하지 않도록 해야 합니다. ↩

이 가이드는 어떠셨나요?

© 2026 DreamNum Co., Ltd.