1. 실시간 주석 데이터는 왜 기존 DB 모델로 다루기 어려운가
SpeakNote에서 다루는 주석 데이터는 일반적인 CRUD 중심의 데이터와 성격이 완전히 다릅니다.
주석은 한 번 생성되고 끝나는 데이터가 아니라, 실시간 음성 인식 결과와 AI 요약 결과가 누적되며 계속 변화하는 흐름형 데이터에 가깝습니다.
하나의 강의 파일에 대해 사용자는 다음과 같은 행동을 반복합니다.
- 음성을 입력하고
- 일정 시간 뒤 AI 주석이 생성되고
- 주석이 PDF 위 특정 좌표에 배치되고
- 필요하면 다시 수정하거나 삭제합니다.
이 과정에서 중요한 점은, “현재 상태”뿐 아니라 “그 시점의 상태” 자체가 의미를 가진다는 점입니다.
즉, SpeakNote에서의 주석은 단순한 레코드가 아니라, 시간에 따라 변화하는 결과 묶음으로 봐야됐습니다.
2. SpeakNote에서 주석을 “스냅샷”으로 정의한 이유
이런 특성 때문에 SpeakNote에서는 주석을
“항상 최신 상태 하나만 유지하는 데이터”로 정의하지 않고,
주석은 특정 시점의 처리 결과를 그대로 보존하는 스냅샷이다.
- 실시간 주석 생성이 한 번 완료될 때마다
- 그 결과 전체를 하나의 문서로 저장
- 이후 수정이 발생하면 새로운 버전으로 저장
이 방식은 저장 용량을 조금 더 사용하지만, 다음과 같은 장점이 있습니다.
- 특정 시점 기준 주석 상태 복원 가능
- 실시간 처리 실패 시에도 이전 결과 보존
- AI 결과의 변경 이력 추적 가능
이러한 요구사항을 만족시키기 위해 선택한 저장소가 MongoDB였습니다.
3. MongoDB 주석 스냅샷 문서 구조 개요
아래는 SpeakNote에서 실제로 사용하는 MongoDB 주석 스냅샷의 개념적 구조입니다.

FileAnnotationDoc
├─ fileId
├─ userId
├─ version
├─ slides[]
│ ├─ pageNumber
│ └─ annotations[]
│ ├─ id
│ ├─ text
│ ├─ position
│ ├─ size
│ ├─ source
│ ├─ order
│ ├─ answerState
│ ├─ createdAt
│ └─ updatedAt
├─ isFinal
├─ createdAt
└─ updatedAt
“이 파일에 대해, 이 사용자에게, 이 시점에 존재했던 주석의 전체 상태”
4. Slide → Annotation 중첩 구조를 선택한 이유
주석을 페이지 단위로 묶은 이유
- PDF는 페이지 단위로 렌더링되고
- 주석은 항상 특정 페이지 위에 존재하며
- 렌더링과 저장 모두 페이지 기준으로 처리됩니다.
따라서 주석을 별도 컬렉션으로 분리해 fileId + pageNumber로 다시 조합하는 구조보다,
페이지 자체를 상위 개념으로 두는 중첩 구조가 훨씬 자연스러웠기 때문에 해당 중첩 구조를 만들었습니다.
- 특정 페이지 주석만 빠르게 로드 가능
- PDF 렌더링과 데이터 구조의 1:1 대응
- 페이지 단위 스냅샷 관리 용이
5. Position / Size를 분리한 좌표 설계 의도
Annotation 내부에서 위치 정보는 다음과 같이 분리되어 있습니다.
position { x, y }
size { width, height }
PDF 좌표 매핑 정확도를 유지하기 위한 설계
- position: PDF 페이지 기준 기준점 좌표
- size: 렌더링 시 실제 차지하는 영역
이렇게 분리함으로써,
- 화면 비율이 달라져도 상대 좌표 유지 가능
- PDF 다운로드 시에도 동일한 위치 재현 가능
- 리사이즈/드래그 이벤트 처리 단순화
6. version, isFinal 필드가 의미하는 것
version 필드는 단순한 숫자가 아닙니다.
이 값은 주석 처리 흐름의 단계를 의미합니다.
- 실시간 처리 중 생성된 임시 결과
- 일정 단위로 확정된 결과
- 최종 저장용 결과
isFinal 필드는 이 중
“이 스냅샷이 최종 결과인지”를 명시적으로 구분하기 위해 사용됩니다.
이를 통해,
- 실시간 렌더링용 데이터
- 다운로드 및 보존용 데이터
를 명확히 분리할 수 있었습니다.
7. 이 스키마가 실시간 시스템에 주는 효과
이 MongoDB 스냅샷 스키마는
SpeakNote의 실시간 아키텍처와 매우 강하게 결합되어 있습니다.
- 실시간 처리 중에도 DB 구조 변경 없음
- 저장 지연이 실시간 렌더링에 영향 없음
- 세션 단위 처리 결과를 그대로 보존 가능
즉, MongoDB는 단순한 비정형 데이터 저장소가 아니라,
실시간 파이프라인의 결과를 안전하게 흡수하는 완충 지점 역할을 수행합니다.
8. PDF 좌표 기반 주석 매핑 전략
SpeakNote에서 주석은 단순한 텍스트 데이터가 아니라, PDF 문서 위의 특정 위치에 정확히 대응되어야 하는 시각적 객체입니다. 따라서 주석 저장에서 가장 중요한 요소 중 하나는 “어떤 좌표 체계로 위치를 기록할 것인가”였습니다.
초기 설계 단계에서 가장 먼저 배제한 방식은 픽셀(px) 기반 좌표 저장이었습니다. 픽셀 좌표는 화면 해상도, 브라우저 크기, PDF 렌더링 배율에 따라 쉽게 달라지기 때문에, 동일한 주석이라도 환경이 바뀌면 위치가 어긋나는 문제가 발생합니다. 특히 SpeakNote는 웹 환경에서 실시간으로 주석을 생성하고, 이후 동일한 주석을 PDF로 다운로드해야 하는 요구사항이 있었기 때문에, 화면 의존적인 좌표 체계는 적합하지 않았습니다.
이에 따라 SpeakNote에서는 PDF 페이지 기준의 정규화 좌표(normalized coordinate) 방식을 채택하였습니다. 모든 주석의 위치와 크기는 페이지의 실제 크기를 기준으로 0~1 사이의 상대값으로 저장되며, 이는 다음과 같은 형태로 표현됩니다.
- position.x, position.y : 페이지 좌측 상단 기준 상대 좌표
- size.width, size.height : 페이지 대비 상대 크기
이 방식의 핵심 장점은,
PDF가 어떤 해상도나 배율로 렌더링되더라도 동일한 비율로 좌표를 복원할 수 있다는 점입니다. 즉, 웹 화면에서 보이던 주석 위치와 PDF 다운로드 결과물에서의 주석 위치가 구조적으로 동일하게 유지됩니다.
9. 실시간 주석 저장 시점과 스냅샷 분리 설계
SpeakNote의 실시간 주석 처리에서 또 하나 중요한 설계 포인트는,
언제 저장하고, 무엇을 저장할 것인가 에 대한 명확한 기준을 세우는 것이었습니다.
실시간 시스템에서는 다음 두 요구가 항상 충돌합니다.
- 최대한 빠르게 결과를 사용자에게 보여주고 싶다
- 하지만 아직 확정되지 않은 중간 결과를 그대로 저장하고 싶지는 않다
SpeakNote에서는 이 문제를 해결하기 위해,
실시간 렌더링 흐름과 영속 저장 흐름을 의도적으로 분리하였습니다.
실시간 STT 결과와 AI 주석은 먼저 WebSocket을 통해 즉시 클라이언트에 전달되며, 이 단계에서는 데이터베이스 저장이 개입하지 않습니다. 즉, 사용자가 보고 있는 주석은 “현재 세션 기준의 실시간 상태”일 뿐, 곧바로 영속화된 데이터는 아닙니다.
MongoDB에 저장되는 시점은 다음과 같은 조건을 만족할 때로 제한하였습니다.
- 일정 단위의 주석 생성이 완료되었을 때
- 페이지 전환, 발화 종료 등 문맥 단위가 명확해졌을 때
- 사용자가 명시적으로 저장 또는 종료 동작을 수행했을 때
이 시점에만 현재까지의 주석 전체 상태를 하나의 스냅샷 문서로 저장합니다.
즉, MongoDB에는 “조각난 변경 사항”이 아니라, 항상 일관된 전체 결과만 기록됩니다.
이 설계 덕분에,
- 실시간 처리 중 DB I/O로 인한 지연이 발생하지 않고
- 저장 실패가 실시간 경험에 직접적인 영향을 주지 않으며
- 언제든 특정 시점의 결과를 그대로 복원할 수 있는 구조
를 동시에 만족할 수 있었습니다.
10. 실시간 주석 수정·삭제가 스냅샷에 반영되는 방식
SpeakNote에서는 사용자가 생성된 주석을 직접 수정하거나 삭제할 수 있으며, 이 역시 실시간으로 반영됩니다. 다만 이 변경 역시 기존 스냅샷을 즉시 수정하는 방식은 사용하지 않습니다.
주석 수정 또는 삭제는 다음과 같은 흐름으로 처리됩니다.
- 사용자의 수정/삭제 동작은 실시간 상태(SessionCtx)에 반영
- 화면에는 즉시 변경된 결과가 렌더링
- MongoDB에는 아직 반영하지 않음
- 다음 저장 시점에, 변경된 상태 전체를 새로운 스냅샷으로 저장
이 방식은 “이벤트 단위 저장”이 아닌 “상태 단위 저장”에 가깝습니다.
즉, “이 주석을 수정했다”라는 로그를 남기는 것이 아니라,
이 시점의 주석 전체 상태는 이렇다 라는 결과를 기록합니다.
이를 위해 Annotation 객체에는 다음과 같은 필드들이 포함됩니다.
- id : 주석의 논리적 식별자
- order : 동일 페이지 내 정렬 순서
- answerState : 처리 상태 또는 검토 상태
- updatedAt : 마지막 수정 시점
삭제의 경우에도 실제로 배열에서 즉시 제거되기보다는,
상태 값이나 스냅샷 버전 변화로 관리하여, 이전 버전 복원이 가능하도록 설계되어 있습니다.
이러한 스냅샷 기반 반영 방식은 구현 난이도는 높지만, 다음과 같은 이점을 제공합니다.
- 실시간 수정 중 오류 발생 시 이전 상태로 복원 가능
- AI 주석 품질 비교 및 재처리 가능
- 협업/히스토리 기능 확장에 유리
11. 정리하며
PDF 좌표 매핑, 저장 시점 분리, 수정/삭제 반영 방식은 각각 독립된 문제처럼 보이지만, SpeakNote에서는 모두 하나의 방향성을 공유합니다.
실시간 처리 흐름을 방해하지 않으면서,
결과는 언제든지 복원 가능해야 한다.
MongoDB 스냅샷 설계는 단순히 “비정형 데이터를 담기 위해” 선택된 것이 아니라,
실시간 시스템의 불확실성을 흡수하기 위한 전략적 선택이었습니다.
이 구조 덕분에 SpeakNote는 실시간성, 안정성, 확장성이라는 세 가지 목표를 동시에 만족할 수 있었으며, 이후 협업 주석, 히스토리 비교, AI 재학습 데이터 활용과 같은 확장 가능성도 자연스럽게 열어둘 수 있었습니다.
'ServerDev > Database' 카테고리의 다른 글
| [DataBase - Design] MySQL + MongoDB를 분리한 이유 (Project: SpeakNote) (0) | 2025.12.22 |
|---|