# 데이터베이스 어댑터

> 협업 영속성 계약을 이해하고 Memory, SQLite, 사용자 정의 Database Adapter 중에서 선택합니다.

- Human documentation: [https://docs.univer.ai/ko-KR/server/collaboration/database-adapters](https://docs.univer.ai/ko-KR/server/collaboration/database-adapters)

- Agent Markdown: [https://docs.univer.ai/ko-KR/server/collaboration/database-adapters.md](https://docs.univer.ai/ko-KR/server/collaboration/database-adapters.md)

- Requested language: `ko-KR`

- Content language: `ko-KR`

- Documentation version: `1.0.4`

- Source: [collaboration/database-adapters.ko-KR.mdx](https://github.com/dream-num/documentation/blob/dev/content/server/collaboration/database-adapters.ko-KR.mdx)

---

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](https://github.com/dream-num/univer-collaboration-examples/blob/main/examples/database-adapter/server/main.ts)를 참고하세요.

## Adapter가 저장하는 데이터

핵심 협업 Adapter는 다음을 저장합니다.

* Unit 레코드와 현재 최신 리비전
* 리비전이 지정된 스냅샷
* 확정된 변경 집합
* Sheet 스냅샷에서 참조하는 블록
* `(unitID, sid, reqId)`를 키로 하는 제출 멱등성[^submission-idempotency] 레코드
* Unit의 논리 삭제, 복구, 영구 삭제 상태

기본적으로 사용자, 역할, ACL, 디렉터리, 파일, 제품 메타데이터는 저장하지 않습니다. History, Thread Comment, Worktree는 독립적인 Adapter 계약을 사용합니다.

## 필수 동작 의미

`IDatabaseAdapter`는 단순한 CRUD 인터페이스가 아닙니다. 구현은 Collaboration Service가 기대하는 정확성 계약을 지켜야 합니다.

| 계약                     | 의미                                             |
| ---------------------- | ---------------------------------------------- |
| 원자적 생성                 | 초기 스냅샷과 Sheet 블록이 함께 보이거나 모두 보이지 않음            |
| 리비전 CAS[^revision-cas] | 최신 리비전이 기대값과 일치할 때만 다음 제출 확정                   |
| 제출 멱등성                 | 동일한 `(unitID, sid, reqId)`를 재시도하면 동일한 확정 결과 반환 |
| 연속 변경 집합               | 확정된 변경 집합과 Unit 리비전이 연속성을 유지                   |
| 스냅샷 가시성                | 모든 의존 데이터가 저장된 후에만 스냅샷 읽기 가능                   |
| 원자적 수명 주기 작업           | 삭제·복구 배치의 모든 Unit이 성공하거나 모두 적용되지 않음            |
| 영구 삭제 표식               | 영구 삭제한 Unit ID는 다시 생성할 수 없음                    |

이러한 동시성과 원자성 없이 메서드를 일반 DB 읽기·쓰기에 매핑하면 OT와 복구 동작이 깨집니다.

## Memory Adapter

`@univerjs-pro/collaboration-database-memory`는 프로세스 내부 구현으로 다음에 적합합니다.

* 단위 및 통합 테스트
* 공식 예제
* 임시 개발 환경
* 사용자 정의 Middleware나 클라이언트 구성의 빠른 검증

```ts
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 구현입니다.

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

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

```ts
await service.dispose();
await database.dispose();
```

실행하며 비교할 수 있는 [데이터베이스 어댑터 예제](https://docs.univer.ai/ko-KR/server/collaboration/examples.md#database-adapter)를 참고하세요.

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

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