Obsidian LiveSync 동기화 중단 원인과 해결
Self-hosted LiveSync + CouchDB 환경에서 노트북과 모바일 모두 동기화가 멈춘 사례.
표면에 드러난 오류 메시지는 원인이 아니었고, 진짜 원인은 서버 측milestone문서의 잠금 상태였다.
목차
1. 개요
Self-hosted LiveSync는 Obsidian 볼트를 자체 CouchDB 서버와 동기화하는 플러그인이다. 이 글은 정상 동작하던 환경에서 접속 주소(URI)를 변경한 뒤 모든 기기의 동기화가 멈춘 문제를 다룬다.
핵심 결론을 먼저 적는다:
| 항목 | 내용 |
|---|---|
| 표면 증상 | 연결·인증은 성공하는데 문서가 전혀 오가지 않음 |
| 오해했던 원인 | 권한 부족(403), CORS 설정, 계정 종류 |
| 실제 원인 | _local/obsydian_livesync_milestone 문서의 locked: true + 노드 ID 불일치 |
| 해결 | 잠금 해제 및 신규 노드 ID를 승인 목록에 추가 |
| 소요 | 원인 규명 대부분, 실제 수정은 요청 1회 |
2. 증상
- 모바일에서 설정 마법사 중 “액세스가 금지되었습니다. 테스트를 계속할 수 없습니다.” 표시
- 설정을 저장해도 플러그인 내부 상태가
isConfigured: false로 남음 - 이후 정상 동작하던 노트북까지 동기화 중단
- 노트북 재시도 시 다음 메시지:
The remote database has been rebuilt or corrupted since we have synchronized last time.
Fetch rebuilt DB, explicit unlocking or chunk clean-up is required.
특히 혼란스러웠던 점은 서버 로그상 연결과 인증이 모두 성공하고 있었다는 것이다.
3. 환경 구성
| 구성요소 | 내용 |
|---|---|
| 서버 | CouchDB 3.4 (Docker) |
| 노출 경로 | VPN 터널 내부 전용 사설 IP — 공인 인터넷 비노출 |
| TLS | 리버스 프록시(nginx) + Let’s Encrypt (DNS-01 발급) |
| 계정 | 서버 관리자 계정과, 동기화 전용 DB에만 권한이 있는 비관리자 계정으로 분리 |
| 클라이언트 | 데스크톱(Electron), Android |
모바일 Obsidian은 평문 HTTP 연결을 허용하지 않으므로, 유효한 인증서를 갖춘 HTTPS 주소가 필요하다. 도메인을 둔 이유가 이것이다. DNS-01 방식으로 발급하면 서버를 인터넷에 노출하지 않고도 인증서를 받을 수 있다.
4. 오진했던 단서들
원인 규명이 오래 걸린 이유는, 그럴듯하지만 무관한 단서가 연속으로 나타났기 때문이다. 기록해 둘 가치가 있다.
4.1 “액세스가 금지되었습니다” (403)
가장 눈에 띄는 빨간 오류였지만 원인이 아니었다.
비관리자 계정으로 접속하면 플러그인의 “데이터베이스 구성 확인” 단계가 서버 설정 조회를 시도하는데, 이 조회는 관리자 권한을 요구한다. 즉 비관리자 계정에서는 정상적으로 발생하는 예상된 실패이며, 업스트림에도 알려진 동작이다. 실제 동기화 자체는 비관리자 계정으로도 문제없이 동작한다.
플러그인 UI에도 이 취지의 경고가 적혀 있다: “administrator access may be required.”
교훈: 오류 메시지가 빨간색이라고 해서 그것이 현재 문제의 원인인 것은 아니다. 선택적(optional) 진단의 실패와 기능적 실패를 구분해야 한다.
4.2 CORS 설정 의심
모바일 앱은 웹뷰 기반이라 CORS 영향을 받는다. 그래서 Access-Control-Allow-Headers에 authorization이 빠졌을 가능성을 의심했으나, 실제 설정을 조회해 보니 이미 완비되어 있었다.
origins: app://obsidian.md, capacitor://localhost, http://localhost
credentials: true
methods: GET, PUT, POST, HEAD, DELETE
headers: accept, authorization, content-type, origin, referer, cache-control
서버 로그의 OPTIONS 프리플라이트 요청이 모두 204로 성공하고 있었던 것도 CORS가 정상이라는 방증이었다.
4.3 계정 종류(관리자 vs 비관리자)
관리자 계정으로 바꾸자 “모든 검사를 통과했습니다!” 가 표시되어 해결된 것처럼 보였다. 그러나 동기화는 여전히 되지 않았다. 즉 계정 문제는 4.1의 진단 실패만 없앴을 뿐, 실제 원인과는 무관했다.
5. 원인 규명 과정
방향을 바꿔, 클라이언트 UI가 아니라 서버가 실제로 무엇을 주고받는지 확인했다.
5.1 리버스 프록시 접근 로그 확인
10.20.x.x admin GET /db/_local/obsidian_livesync_sync_parameters 304
10.20.x.x admin GET /db/ 200
10.20.x.x admin GET /db/obsydian_livesync_version 304
10.20.x.x admin GET /db/_local/obsydian_livesync_milestone 200
10.20.x.x admin PUT /db/_local/obsydian_livesync_milestone 201
← 여기서 끊김
이 로그가 결정적이었다.
- 연결·인증·CORS 모두 성공 (
200/201/204/304, 오류 로그 없음) - 그러나 핸드셰이크 직후 중단
- 정상이라면 이어서
_changes,_revs_diff,_bulk_get요청이 나와야 하는데 전혀 없음
즉 “연결은 되는데 복제를 시작하지 않는” 상태였다. 권한이나 네트워크 문제라면 오류 응답이 남았을 텐데 그렇지 않았다.
5.2 능동적 검증
“동기화할 내용이 없어서 조용한 것”일 가능성을 배제하기 위해, 서버에 테스트 노트를 직접 생성하고 반응을 관찰했다.
- 서버
update_seq: 5616 → 5618 (변경 발생 확인) - 그러나 클라이언트는 1분 40초가 지나도 무반응
LiveSync 모드라면 즉시 감지해야 한다. 이로써 복제가 실제로 멈춰 있다는 것이 확정됐다.
5.3 milestone 문서 조사
로그가 마지막으로 가리킨 _local/obsydian_livesync_milestone을 직접 열어보았다.
{
"locked": true,
"accepted_nodes": ["2a3l9t254j", "gb1fb2nz9s", "ud4d2s4d57", "jor24ce53u"],
"node_info": {
"ud4d2s4d57": { "device_name": "노트북", "last_connected": "8/16" },
"7zcjfioz68": { "device_name": "노트북", "last_connected": "8/27" },
"jor24ce53u": { "device_name": "모바일", "last_connected": "8/16" }
}
}여기서 두 가지가 동시에 보였다.
locked: true— 데이터베이스가 잠금 상태- 현재 접속 중인 노트북의 노드 ID
7zcjfioz68이accepted_nodes에 없음 - 같은
device_name을 가진 노드가 두 개(ud4d2s4d57,7zcjfioz68) 존재
6. 근본 원인
LiveSync는 원격 DB가 재구축(rebuild)되었을 때 기존 기기들이 손상된 상태로 동기화하는 것을 막기 위해, milestone 문서에 잠금 플래그와 승인된 노드 목록을 둔다.
사건의 순서는 다음과 같았다.
① 접속 URI 변경 (http://내부IP → https://도메인)
↓
② 플러그인이 로컬 데이터베이스를 재구성
↓
③ 재구성된 로컬 DB에 새 노드 ID 발급 (ud4d2s4d57 → 7zcjfioz68)
↓
④ 새 노드 ID는 accepted_nodes 목록에 없음
↓
⑤ locked: true 상태이므로 미승인 노드의 복제 거부
↓
⑥ 핸드셰이크는 성공하지만 복제는 시작되지 않음 (조용한 실패)
노드 ID는 기기가 아니라 로컬 데이터베이스에 부여된다. 따라서 로컬 DB가 재생성되면 같은 기기라도 새로운 노드로 인식된다. 이것이 “잘 되던 노트북까지 갑자기 멈춘” 이유였다.
모바일이 계속 실패하던 것도 동일한 잠금 때문이었다. 4장의 403 메시지는 이와 무관한 곁가지였고, 그것을 원인으로 오해해 시간을 소모했다.
7. 해결 방법
오류 메시지가 제시한 세 가지 선택지는 다음과 같았다.
| 방법 | 설명 | 비고 |
|---|---|---|
| Fetch rebuilt DB | 기기가 원격 데이터를 전부 다시 받음 | 클라이언트에서 수행. 가장 표준적 |
| explicit unlocking | 서버의 잠금을 해제 | 이번에 선택 |
| chunk clean-up | 청크 정리 | 대규모 정합성 문제 시 |
원격 데이터가 온전하다는 것을 이미 확인했으므로 잠금 해제를 택했다.
7.1 사전 조치 — 백업
무엇을 하든 되돌릴 수 있어야 한다. 수정 전에 두 가지를 확보했다.
- CouchDB 전체 문서 덤프 (JSON)
- 볼트 노트를 사람이 읽을 수 있는 마크다운 파일로 내보내기
- 수정 대상인 milestone 문서 자체의 사본
7.2 적용
_local/obsydian_livesync_milestone 문서를 다음과 같이 수정했다.
locked→falseaccepted_nodes→node_info에 존재하는 모든 노드 ID를 포함하도록 확장
locked: true → false
accepted_nodes: 4개 → 5개 (신규 노드 ID 추가)
_local/문서는 복제되지 않는 로컬 전용 문서이며, 갱신 시_rev를 함께 전달해야 한다.
8. 검증
수정 직후 클라이언트에서 복제를 재시도하자, 로그에 정상 복제 흐름이 나타났다.
_changes since=0 → 전체 스윕 시작
_changes since=457
_changes since=1383
_changes since=2998
_changes since=5618 → 최신까지 도달
_bulk_get / _revs_diff → 문서 본문 수신
_changes heartbeat=30000 → 지속 연결 수립 (실시간 동기화 가동)
이어서 모바일도 접속해 since=5616 → 5618로 따라잡았다.
최종 상태:
| 기기 | 진행도 | 서버 |
|---|---|---|
| 노트북 | 5618 | 5618 ✅ |
| 모바일 | 5618 | 5618 ✅ |
서버에서 생성했던 테스트 노트가 양쪽 기기에 모두 나타나는 것으로 양방향 동기화를 확인했다.
9. 교훈과 재발 방지
9.1 진단에 대하여
- 오류 메시지의 크기와 원인으로서의 중요도는 비례하지 않는다. 가장 눈에 띄던 403은 무관한 곁가지였다.
- 선택적 진단의 실패와 기능적 실패를 구분하라. 플러그인이 “관리자 권한이 필요할 수 있음”이라고 명시한 검사는, 실패해도 무방한 경우가 있다.
- 클라이언트 UI에서 막히면 서버 로그로 이동하라. 접근 로그의 요청 흐름이 끊긴 지점이 가장 정확한 단서였다.
- 조용한 실패를 능동적으로 검증하라. “동기화할 게 없어서 조용한 것”과 “멈춘 것”은 겉보기에 같다. 변경을 일부러 만들어 반응을 관찰해야 구분된다.
9.2 운영에 대하여
- 접속 URI 변경은 로컬 DB 재구성을 유발할 수 있다. 잘 동작하는 기기의 설정은 필요 없이 건드리지 않는 편이 낫다. 이번 문제도 “주소를 통일하면 깔끔하겠다”는 불필요한 변경에서 시작됐다.
- 기기를 추가할 때는 설정 URI(Setup URI) 방식을 우선 고려하라. 동작 중인 기기에서 설정을 통째로 내보내 주입하는 방식이라, 마법사를 수동으로 통과하다 발생하는 실수를 줄인다.
- 초기 연결 시 방향 선택에 주의하라. “이 기기의 데이터로 서버를 초기화”를 선택하면 서버 데이터가 덮어써진다. 기존 서버에 참여하는 경우에는 반드시 원격에서 가져오는(Fetch) 쪽을 택해야 한다.
- 구조를 건드리기 전에 백업하라. 이번에는 백업이 있었기에 잠금 해제 같은 조치를 주저 없이 시도할 수 있었다.
9.3 빠른 점검 순서
동일 증상이 재현될 경우 다음 순서로 확인하면 빠르다.
- 리버스 프록시 접근 로그에 요청이 도달하는가? → 네트워크/DNS/VPN 확인
- 인증이 성공하는가(
200/201)? → 계정·권한 확인 - 핸드셰이크 이후
_changes가 나타나는가? → 없다면 milestone 잠금 의심 _local/obsydian_livesync_milestone의locked와accepted_nodes확인- 현재 기기의 노드 ID가 승인 목록에 있는가?
10. 참고 자료
- Self-hosted LiveSync — https://github.com/vrtmrz/obsidian-livesync
- 비관리자 계정에서의 연결 테스트 실패 이슈 — https://github.com/vrtmrz/obsidian-livesync/issues/988
- 관리자 권한 필요 여부 논의 — https://github.com/vrtmrz/obsidian-livesync/discussions/582
- CouchDB 문서 — https://docs.couchdb.org/