Going local-first with Automerge and Convex
Quick Summary
이 글은 Automerge의 CRDT 기반 로컬 우선 편집 모델과 Convex 백엔드를 함께 사용해 오프라인 편집, 충돌 없는 협업, 서버 기반 권한·관계·동기화를 결합하는 방법을 설명한다.
🖼️ 인포그래픽
🖼️ 4컷 인포그래픽
💡 한 줄 요약
이 글은 Automerge의 CRDT 기반 로컬 우선 편집 모델과 Convex 백엔드를 함께 사용해 오프라인 편집, 충돌 없는 협업, 서버 기반 권한·관계·동기화를 결합하는 방법을 설명한다.
📌 핵심 요약
- 로컬 우선 접근은 모든 변경을 서버 응답에 의존하지 않게 하여 더 빠르고 일관된 편집 경험을 제공하며, 네트워크가 느리거나 끊겨도 사용자가 계속 작업할 수 있게 한다.
- Automerge는 JSON 문서 변경을 CRDT로 기록·병합하고 IndexedDB 등에 저장해 브라우저 종료나 컴퓨터 재시작 후에도 로컬 변경과 이력이 살아 있도록 돕는다.
- CRDT는 여러 사용자가 서로 다른 시점에 같은 문서를 편집해도 변경을 병합할 수 있지만, 로컬 우선 설계는 분산 상태 문제를 동반하므로 인지적 비용과 일관성 요구를 신중히 고려해야 한다.
- Automerge 내부는 git과 유사하게 변경 해시, 부모 관계, heads 개념을 사용하며, 스냅샷과 증분 변경을 통해 전체 이력 또는 최근 변경만 저장·전송할 수 있다.
- Convex를 함께 사용하면 CRDT 문서의 동기화뿐 아니라 인증, 권한 부여, 관계형 데이터 연결, 서버 주도 워크플로, 서버 사이드 렌더링용 데이터 로딩 같은 백엔드 기능을 보완할 수 있다.
🧩 주요 포인트
- 로컬 우선 접근은 모든 변경을 서버 응답에 의존하지 않게 하여 더 빠르고 일관된 편집 경험을 제공하며, 네트워크가 느리거나 끊겨도 사용자가 계속 작업할 수 있게 한다.
- Automerge는 JSON 문서 변경을 CRDT로 기록·병합하고 IndexedDB 등에 저장해 브라우저 종료나 컴퓨터 재시작 후에도 로컬 변경과 이력이 살아 있도록 돕는다.
- CRDT는 여러 사용자가 서로 다른 시점에 같은 문서를 편집해도 변경을 병합할 수 있지만, 로컬 우선 설계는 분산 상태 문제를 동반하므로 인지적 비용과 일관성 요구를 신중히 고려해야 한다.
- Automerge 내부는 git과 유사하게 변경 해시, 부모 관계, heads 개념을 사용하며, 스냅샷과 증분 변경을 통해 전체 이력 또는 최근 변경만 저장·전송할 수 있다.
- Convex를 함께 사용하면 CRDT 문서의 동기화뿐 아니라 인증, 권한 부여, 관계형 데이터 연결, 서버 주도 워크플로, 서버 사이드 렌더링용 데이터 로딩 같은 백엔드 기능을 보완할 수 있다.
🧠 상세 정리
1. 로컬 우선 편집이 해결하려는 문제
글은 Convex와 Automerge를 함께 사용해 로컬 우선 텍스트 편집 경험을 만드는 문제에서 출발한다. 목표는 여러 사람이 동시에 협업해도 서로의 변경을 덮어쓰지 않고, 네트워크가 느리거나 간헐적으로 끊기거나 완전히 사라져도 계속 편집할 수 있게 하는 것이다. 또한 브라우저를 닫거나 컴퓨터를 재시작해도 로컬 변경이 사라지지 않고, 나중에 인터넷이 연결된 상태로 문서를 열면 다시 동기화되는 흐름을 중요하게 본다. 즉 핵심은 서버가 항상 즉시 응답해야만 앱이 동작하는 구조에서 벗어나, 사용자가 체감하는 편집 경험을 안정적이고 즉각적으로 만드는 데 있다.
2. 왜 로컬 우선인가
저자는 앱의 모든 변경이 서버 로딩과 승인에 묶이면 사용자 경험이 나빠지고 오프라인 워크플로가 제한된다고 설명한다. 데이터를 로컬에 저장하고 편집하면 입력과 화면 반응이 더 빠르고 일관되며, 이후 변경을 다른 클라이언트에 동기화하면 협업과 다중 기기 사용까지 지원할 수 있다. 여러 사용자가 같은 데이터를 수정할 때는 CRDT 같은 구조가 분산된 변경을 병합하는 데 쓰일 수 있으며, 특히 텍스트 편집에서는 대체로 합리적인 결과 문자열을 만들어내는 데 강점을 보인다. 다만 저자는 Automerge와 Yjs 같은 CRDT 구현을 언급하면서도, 로컬 우선 UX를 만드는 방법이 CRDT 하나뿐이라고 보지는 않는다.
3. 분산 상태 문제에 대한 주의
글은 초반부터 로컬 우선 설계가 마법 같은 사용자 경험을 줄 수 있지만, 동시에 문제를 분산 시스템 문제로 바꿀 수 있다고 경고한다. 서버가 하나의 진실 공급원처럼 모든 순서와 정합성을 통제하던 구조와 달리, 여러 클라이언트가 각자 로컬에서 변경을 만들고 나중에 합쳐야 하기 때문이다. 따라서 어떤 도구를 도입할지, 그 도구가 앱의 나머지 데이터와 백엔드 흐름에 어떻게 통합될지를 충분히 고민해야 한다. 저자는 모든 것을 로컬 우선으로 만들 필요는 없으며, 일관성·정확성·편의성이 중요한 부분에서는 서버를 계속 관여시키는 선택도 가능하다고 본다.
4. Automerge가 제공하는 기능
Automerge는 협업 애플리케이션을 만들기 위한 데이터 구조 라이브러리이자 CRDT 구현으로 소개된다. 문자열뿐 아니라 JSON 문서에 대한 변경을 포착하고 이를 다른 클라이언트에 적용할 수 있는 압축 바이너리 형식으로 인코딩한다. IndexedDBStorageAdapter 같은 저장 어댑터를 사용하면 브라우저 안에 문서 내용과 변경 이력을 저장할 수 있어, 오프라인 상태에서도 읽고 쓸 수 있으며 브라우저나 컴퓨터 재시작 후에도 데이터가 유지된다. 또한 여러 클라이언트가 따로 만든 편집을 하나의 문서 버전으로 병합하고, 업데이트를 받은 순서와 관계없이 클라이언트들이 같은 결과에 도달하도록 하는 병합 로직을 제공한다.
5. 충돌 해결, 중복 적용, 탭 간 동기화
Automerge는 충돌이 발생했을 때 합리적인 기본값을 제공하고, 필요한 경우 하나를 선택하는 식의 의견 있는 충돌 해결 로직을 포함한다. 모든 상황에서 도메인 의미까지 완벽히 이해해 해결하는 것은 아니지만, 특정 사용 사례에서는 이런 기본 동작이 충분히 실용적일 수 있다. 또한 인코딩된 변경에는 변경 이력이 포함되어 있어 이미 적용된 변경을 다시 적용하더라도 중복을 감지하고 안전하게 아무 일도 하지 않게 만들 수 있다. 완전한 분산 앱을 만들지 않더라도 MessageChannel이나 BroadcastChannel 어댑터를 이용하면 여러 브라우저 탭 사이에서 변경을 동기화해, 오프라인 편집 중에도 탭들이 같은 상태를 유지할 수 있다.
6. 내부 모델: heads, 스냅샷, 증분 변경
Automerge 내부 구조는 git과 비슷하게 각 변경이 해시와 부모 관계를 가지며, 변경들은 해시의 방향 그래프로 표현된다. 문서의 특정 이력 지점을 참조할 때는 getHeads(doc)를 호출하며, 여기서 heads는 단일 해시가 아니라 해시 배열이라는 점이 git 커밋과 다르다. 저장 인터페이스는 스냅샷과 증분 변경을 구분하지만, 변경 의존성이 인코딩되어 있기 때문에 같은 변경이 여러 위치에 포함되어 있어도 안전하게 처리된다. 스냅샷은 전체 이력을 포함하고, 증분 변경은 getLastLocalChange나 getChanges를 통해 최근 변경만 저장·전송한 뒤 applyChanges나 loadIncremental로 적용할 수 있다.
7. 액터와 데이터 구조 설계 팁
Automerge의 각 변경은 액터에 귀속되며, 저자는 이를 브라우저 탭에 가까운 개념으로 설명한다. 따라서 액터는 사용자와 정확히 일치하지 않고, 한 사용자가 여러 탭을 열면 여러 액터로 나타날 수 있으며 안정적인 사용자 정체성으로 신뢰해서는 안 된다. 데이터 구조를 설계할 때는 배열 안 객체를 인덱스로 식별하기보다 각 객체에 무작위 ID를 만들어 안정적으로 추적하는 것이 권장된다. 이렇게 하면 React 같은 환경에서 다른 사용자가 새 요소를 만드는 중에도 안정적인 key를 사용할 수 있고, 방금 생성한 데이터를 일관되게 찾아 포커스를 맞출 수 있다.
8. 마이그레이션, 외래 키, Convex와의 결합
저자는 CRDT 안의 데이터 형식과 마이그레이션 책임이 개발자에게 있다고 강조한다. 바이너리 인코딩은 데이터베이스 계층에서 검증되는 것이 아니므로, 새 문서뿐 아니라 데이터베이스에 저장된 기존 문서와 아직 동기화되지 않은 클라이언트의 문서까지 고려해야 한다. Automerge 문서 ID는 다른 데이터의 외래 키처럼 사용할 수 있으며, 일반 데이터베이스 문서에 저장하면 인덱스, 관련 문서 조회, 유일성 강제, 권한 확인 같은 표준 백엔드 기능을 활용할 수 있다. Convex를 함께 쓰면 사용자가 동시에 온라인일 필요 없이 변경을 중계하고, 인증·권한 부여·구조적 관계·서버 주도 작업·서버 사이드 렌더링용 데이터 로딩을 CRDT 기반 편집과 결합할 수 있다.
🧾 핵심 주장 / 시사점
- 로컬 우선은 단순한 캐싱 전략이 아니라, 서버 응답 지연과 네트워크 실패를 사용자 경험의 중심 경로에서 밀어내는 애플리케이션 설계 관점이다.
- Automerge는 충돌 병합과 오프라인 지속성을 크게 단순화하지만, 스키마 변경·문서 간 일관성·액터 정체성 같은 문제까지 자동으로 해결해 주지는 않는다.
- Convex와 같은 중앙 백엔드는 CRDT가 약한 인증, 권한, 관계형 조회, 서버 워크플로 영역을 보완하므로, 로컬 우선과 서버 중심 설계를 이분법으로 나누지 않는 접근이 실용적이다.
✅ 액션 아이템
- 로컬 우선 모델을 적용할 때 네트워크 단절 구간의 편집 지속성, 응답 지연, 충돌 병합 결과를 모두 측정해 기준을 정한다.
- Automerge의 변경 해시·부모·heads 이력 모델과 스냅샷·증분 전송 방식을 결합해 저장 범위와 동기화 범위를 기능 단위로 구분한다.
- Convex의 인증·권한·관계형 연결·SSR 로딩 기능을 함께 사용해 CRDT 동기화 경로와 서버 주도 흐름의 권한·일관성 경계를 분리해 설계한다.
❓ 열린 질문
- 오프라인 작업 후 재연결 시 충돌 병합 규칙은 사용자에게 충분히 예측 가능한 결과를 보장할 수 있는가?
- IndexedDB에 남은 로컬 변경과 이력은 브라우저 재시작·기기 변경 후에도 어떤 기간과 범위까지 복구 가능한가?
- 로컬 우선 구조에서 분산 상태의 인지적 비용을 낮추기 위해 어떤 일관성 요구 수준을 운영 타이밍별로 달리 적용해야 하는가?