구글 문서를 열어놓고 친구랑 같이 고쳐본 적 있으세요? 내가 앞 문단을 손보는 동안 친구는 뒷문단을 고치고, 서로의 커서가 색깔별로 움직이는 그 신기한 경험 말이에요. 이걸 협업 편집(실시간 동시 편집)이라고 해요. "우리 에디터에도 저거 넣어주세요"라는 요청, 실무에서 정말 자주 나와요. 그런데 이건 저장 버튼 하나 다는 것과는 차원이 다른 난이도예요. 오늘은 코드를 많이 짜기보다, 이게 왜 어렵고 어떤 원리로 풀리는지 큰 그림을 잡아볼게요. 개념만 잡아도 나중에 라이브러리를 훨씬 잘 쓸 수 있거든요.

27.1 동시에 같은 문서를 고치면 무슨 일이 나나요?

상황을 그려볼게요. 문서에 "고양이"라는 세 글자가 있어요. 지훈이는 맨 앞에 "우리 "를 붙이려 하고, 동시에 수아는 맨 뒤에 "야"를 붙이려 해요. 둘 다 "고양이"를 기준으로 편집을 시작했죠. 각자 자기 화면에선 결과가 잘 나와요. 지훈이 화면엔 "우리 고양이", 수아 화면엔 "고양이야".


문제는 이 두 편집을 하나로 합칠 때 생겨요. 원하는 결과는 당연히 "우리 고양이야"죠. 그런데 순진하게 만들면 엉뚱한 게 나와요. 왜 그런지 다음 칸에서 볼게요. 핵심은 두 사람이 같은 출발점을 보고 각자 편집했다는 데 있어요. 내가 편집하는 사이 상대의 편집이 이미 문서를 바꿔놨는데, 나는 그걸 모르고 옛날 위치를 기준으로 고치고 있는 거예요.

27.2 왜 그냥 덮어쓰기로는 안 되나요?

가장 쉬운 생각은 이거예요. "누가 저장하면 그 사람 글로 서버를 덮어쓰자." 혼자 쓸 땐 잘 되죠. 그런데 둘이 동시에 쓰면 나중에 저장한 사람이 이긴다는 문제가 있어요. 지훈이가 "우리 고양이"를 저장하고, 0.1초 뒤 수아가 "고양이야"를 저장하면, 서버엔 "고양이야"만 남아요. 지훈이의 "우리 "는 흔적도 없이 사라지죠.


그럼 글자 위치를 그대로 보내면 어떨까요? 수아의 편집은 "3번 자리에 야를 넣어라"예요. 그런데 지훈이가 앞에 "우리 "(세 글자)를 이미 넣어서, 합쳐진 문서에선 "고양이"의 끝이 3번이 아니라 6번으로 밀렸어요. 수아가 보낸 "3번 자리"를 그대로 적용하면 "야"가 엉뚱하게 "고양야이"처럼 중간에 박혀요. 위치가 어긋난 거죠. 그래서 협업 편집은 "상대의 편집 때문에 밀린 만큼 내 편집 위치도 보정"해야 해요. 이 보정을 자동으로 해주는 두 가지 유명한 방법이 OTCRDT예요.

27.3 OT는 어떤 방식이에요?

첫 번째 방법은 OT(Operational Transformation, 조작 변환)예요. 이름이 어렵지만 아이디어는 간단해요. 각자의 편집을 "조작"(operation)이라는 명령으로 표현하고, 서로 겹칠 때 위치를 자동으로 변환해 맞추는 거예요.


아까 예로 돌아갈게요. 지훈이의 조작은 "0번에 우리 를 넣기", 수아의 조작은 "3번에 야를 넣기"였죠. 서버(혹은 상대)는 수아의 조작을 받을 때, "앞에서 지훈이가 세 글자를 넣었네? 그럼 수아의 3번을 6번으로 밀어서 적용하자"라고 변환해요. 그 결과 "야"가 제자리에 붙어 "우리 고양이야"가 완성돼요. 이 위치 보정 규칙이 OT의 심장이에요.


OT는 오래된 방식이고, 실제로 구글 문서가 이 계열로 돌아가요. 강력하지만 약점이 있어요. 변환 규칙을 정확히 짜기가 까다롭고, 대개 중앙 서버가 조작들의 순서를 정리해주는 역할을 맡아야 잘 돌아가요. 그래서 직접 밑바닥부터 OT를 구현하는 건 상당한 고수의 영역이에요. 우리 같은 실무자는 원리만 알고, 검증된 라이브러리를 쓰는 게 정답이에요.

27.4 CRDT는 또 뭔가요?

두 번째 방법은 요즘 인기가 많은 CRDT(Conflict-free Replicated Data Type, 충돌 없는 복제 자료형)예요. 이름을 풀면 "충돌이 원천적으로 안 나게 설계된 데이터 구조"예요. OT가 편집이 겹칠 때마다 위치를 계산해서 보정한다면, CRDT는 아예 글자마다 고유한 표식을 붙여서 위치 계산이 필요 없게 만들어요.


비유하자면 이래요. OT는 "몇 번째 자리"라는 순번으로 글자를 가리켜서, 앞이 바뀌면 순번이 흔들려요. CRDT는 글자 하나하나에 절대 안 바뀌는 이름표(예: "지훈-3번째로 만든 글자")를 붙여요. "야"는 "고양이"의 이 글자 바로 뒤라고 이름표로 지정되니까, 앞에 "우리 "가 몇 글자 끼어들든 상관없이 항상 제자리를 찾아가요. 순번이 아니라 이름표로 가리키니까 위치가 안 흔들리는 거죠.


이 성질 덕분에 CRDT는 중앙 서버가 없어도 각자 병합이 가능해요. 두 사람이 오프라인에서 따로 고친 뒤 나중에 연결돼도, 각자의 편집을 어떤 순서로 합치든 결과가 똑같이 나오도록 수학적으로 보장돼요. 대신 글자마다 이름표를 달다 보니 데이터가 좀 무거워지는 게 흠이에요.

27.5 그래서 OT랑 CRDT 중 뭘 골라요?

둘을 나란히 놓고 볼게요.


OT (조작 변환)
- 편집을 명령으로 보내고 위치를 변환해 맞춤
- 보통 중앙 서버가 순서를 정리해줘야 함
- 데이터가 가벼운 편, 규칙 구현이 까다로움
- 대표: 구글 문서 계열

CRDT (충돌 없는 자료형)
- 글자마다 고유 이름표를 붙여 위치 계산이 불필요
- 중앙 서버 없이도 병합 가능, 오프라인에 강함
- 데이터가 무거운 편, 검증된 라이브러리가 많음
- 대표: Yjs, Automerge


실무에서 직접 밑바닥부터 둘 중 하나를 짜는 일은 거의 없어요. 둘 다 제대로 만들려면 논문 수준의 정확함이 필요하거든요. 그래서 현실적인 답은 잘 만들어진 라이브러리를 가져다 쓰는 것이에요. 그리고 요즘 웹 에디터에서 가장 널리 쓰이는 라이브러리가 CRDT 기반의 Yjs(와이제이에스)예요.

27.6 Yjs는 뭔가요?

Yjs는 협업 편집을 거의 공짜로 얹게 해주는 CRDT 라이브러리예요. 복잡한 위치 보정과 병합을 전부 안에서 처리해줘서, 우리는 공유되는 데이터 상자를 만들어 에디터에 연결만 하면 돼요. 아주 큰 그림만 코드로 보여드릴게요. 지금은 외우지 말고 흐름만 느끼면 돼요.


const doc = new Y.Doc();
const text = doc.getText("content");

const provider = new WebsocketProvider(
"wss://example.com", "room-1", doc
);


여기서 Y.Doc은 여러 사람이 함께 보는 공유 문서 상자예요. doc.getText로 그 안에 글이 담길 공유 텍스트 칸을 하나 꺼내죠. 마지막의 WebsocketProvider는 이 상자를 다른 사람들과 실시간으로 연결해주는 통로예요. 같은 방 이름("room-1")에 접속한 사람들끼리 편집이 오가고, 병합은 Yjs가 알아서 해줘요. 이 text를 우리 에디터에 이어붙이면, 한 사람이 친 글자가 다른 사람 화면에 실시간으로 나타나요. OT냐 CRDT냐를 우리가 신경 쓸 필요 없이, 라이브러리가 그 어려운 걸 대신 해주는 거죠.

27.7 상대의 커서와 이름표는 어떻게 보여줘요?

협업 편집의 꽃은 다른 사람 커서가 색깔별로 움직이는 모습이에요. "지훈이가 지금 이 문장을 보고 있구나"가 눈에 보이면 협업이 훨씬 자연스럽죠. 이걸 위한 개념이 awareness(어웨어니스, 서로의 상태 인식)예요.


awareness는 문서 내용과는 별개의 정보예요. 글 자체가 아니라 "누가 어디에 커서를 두고 있는지", "이름과 색깔은 뭔지" 같은 잠깐 쓰고 버리는 정보죠. 그래서 이건 문서처럼 영구 저장하지 않고, 접속한 사람들끼리 실시간으로만 주고받아요. 사용자가 나가면 그 커서도 스르르 사라지고요. Yjs 같은 라이브러리는 이 awareness 기능도 같이 제공해서, 각자의 커서 위치와 이름표를 넘겨받아 화면에 그리기만 하면 돼요. 원리는 "문서 병합"과 "상태 공유"가 두 개의 다른 통로로 흐른다는 것, 이것만 기억하면 충분해요.

27.8 오늘 정리

협업 편집의 큰 그림을 잡았어요.
1. 여럿이 동시에 고치면 편집 위치가 서로 밀려 그냥 덮어쓰기로는 글이 깨져요.
2. OT는 편집을 명령으로 보고 위치를 변환해 맞추며, 보통 중앙 서버가 순서를 정리해줘요.
3. CRDT는 글자마다 고유 이름표를 붙여 위치 계산 없이 병합되고, 오프라인에 강해요.
4. 둘 다 직접 짜기보다 Yjs 같은 검증된 라이브러리를 쓰는 게 정답이에요.
5. 상대의 커서와 이름표는 문서와 별개인 awareness 통로로 흘러요.


협업 편집은 실무에서 가장 어려운 축에 드는 기능이에요. 그래서 원리를 알고 좋은 라이브러리에 기대는 판단이 실력이에요. 오늘 이 지도를 손에 쥐었으니, 언젠가 협업 편집을 붙일 일이 생겨도 어디서 무엇을 골라야 할지 길을 잃지 않을 거예요. 원리를 아는 사람은 라이브러리를 겁내지 않거든요.