← More Posts

CRDT - 텍스트 문서 실시간 동시 편집의 기본원리

Written by Kim Dong Hun
2022-09-03
Category: #CRDT #Algorithm

How to resolve multiple peers editing the same text context

네이버 인턴 초기에 사용한 임시출입증

학교에서 컴퓨터공학을 전공하는 학생들에게 제공하는 여름방학 체험형 인턴십 덕분에 6월말부터 8월말까지 약 두 달간 네이버에서 개발자로 일할 수 있었다. 지원한 부서는 Yorkie라는 오픈소스 프로젝트를 개발하는 곳이었는데, 해당 프로젝트는 Google Docs나 Figma와 같은 서비스를 만들기 위한 동시문서편집 기술 개발 및 개발 툴 제작이 주요 업무였다. 디자인, 컴퓨터공학, 경영을 삼전공하고 있는 학생으로서 평소에 의미있는 서비스를 개발하는 것에 관심이 많았던 터라, 이번 기회를 통해 동시문서편집의 작동방식을 이해하고 싶어서 해당 부서에 지원했다. Yorkie에 대해서 좀 더 알고 싶으신 독자들은 https://yorkie.dev/ 에 들어가면 더 자세한 내용을 찾을 수 있다.

Yorkie Dev Website

동시문서편집. 사실, 이 기술에 대해서 궁금한 사람이 별로 없을 뿐더러, 애초에 쉽게 이해할 수 있는 개념이 아니다. 그러한 관계로 이 글에서는 전체 작동방식을 설명하지 않고 핵심적인 내용 몇 개만 짚고 넘어가려고 한다.

일단 동시문서편집 기술이란 무엇인가? 첫 문단에서 잠시 언급했지만, 다들 한번씩은 사용해본 적이 있는 Google Docs를 생각해보면 감이 잡힐 것이다. 짧게 설명하자면, 단어 그대로, 동시에 서로가 문서를 편집할 수 있는 기술이다. 즉, 상대방이 무언가를 입력하면, 다른 사용자들(peer)이 그 입력하는 과정을 실시간으로 확인할 수 있고, 상대방이 문서를 편집하는 동시에 본인 또한 무언가를 입력하거나 수정이 가능한 문서 편집 기술이다. 컴퓨터에 대해서 잘 모르더라도, 동시에 같은 것을 작업하는 기술이기 때문에 사용자와 다른 사용자 사이에 정보가 왔다갔다해야 한다는 것은 대충 알 수 있다. 여기에서 기술적 난제는 ‘동시성’에서 발원한다.

기술적 난제에 대해 이야기하기 앞서, 동시성이 결여된 문서편집 기술을 한번 떠올려보자. 가장 대표적인 예시로는 카카오톡 메신저가 있겠다. 카카오톡 메신저는 동시성이 필요없다. 상대방이 나한테 메시지를 보내면, 나는 그것을 받고, 또 나도 메시지를 보낸다. 서로의 행위도 겹칠 일이 없다. 상대방의 입력창은 내가 입력할 수 없고, 나의 입력창 또한 상대방이 입력할 수 없기 때문이다. 문서편집이라는 관점에서 볼 때 기술적 난제가 존재하지 않는다.

image1

이제 동시성이 있는 문서편집을 생각해보자. A와 B가 ‘hello’라고 적혀있는 텍스트 문서를 보고 있다고 하자. A가 ‘hello’ 앞에 ‘y’를 붙이고 B는 ‘hello’ 뒤에 ‘!’를 붙인다고 하자. 이런 경우 A와 B의 입력 영역이 겹치지 않기 때문에 문제가 발생하지 않는다. 정보가 왔다갔다해서 결국 ‘hello’가 ‘yhello!’로 바뀔 것이다. 그럼 문제는 언제 발생하는가? 눈치가 빠른 사람들은 얼추 대답할 수 있을 것이다. 문제는 A와 B가 같은 영역을 수정할 때 발생한다.

image2

A와 B 모두 ‘hello’ 뒤에 문자를 추가한다고 하자. A는 ‘hello’ 뒤에 ‘y’을, B는 ‘hello’ 뒤에 ‘!’를 붙인다고 하자. A는 ‘helloy’을 기대할 것이고 B는 ‘hello!’를 기대할 것이다. 정보를 매개하는 중간자는 이를 해결해야 한다. 누군가는 그냥 단순하게 무조건 A의 행위를 우선시켜 A가 하는 모든 것이 B를 우선시하게 하면 되지 않겠냐고 할 수 있겠다. 그럼 결과물은 ‘helloy!’가 되겠다. 그런데 이렇게 단순하게 풀 수 있는 문제가 아니다. 여러가지 상황을 고려하면 해당 해결책은 적합하지 않다. 정말 단순한 상황을 예시로 들자. 만약 A의 컴퓨터가 느려서 A의 행위가 중간자에게 늦게 전달된다면 어떻게 될까? B는 컴퓨터가 빨라서 ‘! My name is’까지 추가한 사항이 중간자에게 전달되고 나서 A의 ‘y’ 행위가 반영이 된다면? 그럴 경우 ‘helloy! My name is’이 되어야 할 것이 실제로는 ‘hello! My name isy’이 될 것이다. A는 분명 ‘hello’ 뒤에 ‘y’을 붙이려는 의도를 가지고 있었는데, A는 컴퓨터 속도의 지연때문에 본인이 보지도 못한 ‘name is’ 뒤에 ‘y’을 붙이게 되어버린다.

crdt

윗문단에서 언급한 예외적인 상황을 모두 가정하고 그 모든 상황에 대처할 수 있는 해결책(알고리즘)을 제시하는 것이 바로 컴퓨터공학 전공자들의 임무이다. Yorkie는 이 문제를 Conflict-free Replicated Data Type (CRDT)라는 알고리즘에 기반해서 해결한다. CRDT의 핵심은 어떤 수정 행위가 발생했을때 수정 행위가 발생한 물리적인 시간(어떤 행위가 먼저 중간자에게 도달했는가?)과 상관없이 다른 고유값(key)에 기반해 문서를 변경하는 것이다. 물론 이를 가능하게 하기 위해서 사용자들의 수정행위마다 고유한 key를 부여하여 해당 행위를 문서에 반영해야 한다.

image3

이해를 돕기 위해 위에서 언급한 사례에 CRDT를 살짝 적용한 결과물을 보여주겠다. ‘hello’ 각 글자마다 위치 숫자를 부여하자. 즉 h – 0, e – 1, l – 2, l - 3, o – 4라고 하자. A와 B의 각 행위에 이제 key를 부여하겠다. Key에는 문서상 위치 그리고 행위자를 넣는다고 하자. A가 ‘y’를 ‘hello’ 뒤에 붙이면 그 행위는 (5, A, ‘y’)이 된다. B가 ‘!’를 ‘hello’ 뒤에 붙이면 그 행위는 (5, B, ‘!’)가 된다. A와 B의 수정사항이 중간자에게 전달된다고 하자. 그러면 중간자는 (5, A ‘y’)과 (5, B, ‘!’)를 받는다. 이 Key를 통해 각 행위를 반영하는 순서를 정한다. (5, A)가 (5, B) 보다 앞서기에 결과물은 ‘helloy!’가 된다.

image4

CRDT의 힘을 체감하기 위해서 조금 더 복잡한 사례를 가져오겠다. 앞서 얘기한 ‘hello’ 예시를 다시 가져오겠다. ‘hello’의 뒤에 A는 ‘y’를 넣으려 하고 B는 ‘!’를 넣으려고 한다. 근데 B는 또 바로 그 뒤에 ‘ My’를 넣으려고 한다고 하자. 설상가상으로 A의 컴퓨터 속도가 느리다고 하자. 이제 A와 B의 각 행위에 key를 부여하자. 윗문단에서 본 것처럼 A의 행위는 (5, A, ‘y’)이다. B는 행위가 두개다. 첫번째는 (5, B, ‘!’)이고 두번째는 (6, B, ‘ My’)이다. B의 행위들이 시간적으로는 중간자에게 먼저 도달했다고 하자. 그럼 B입장에서 문서는 현재 ‘hello! My’이다. 그러다가 A의 행위가 뒤늦게 들어온다. 들어온 행위가 (5, A, ‘y’)이기 때문에 ‘! My’에서 손을 봐야 한다. ‘hello’ 뒤의 모든 행위를 key 값과 함께 나열하면 (5, B ‘!’), (6, B, ‘ My’), (5, A, ‘y’)이다. (5, A)는 key 순서상 (5, B) 앞에 존재해야 한다. 그 결과 문서의 결과값은 ‘helloy! My’가 되어 컴퓨터의 지연 시간과 상관없이 문서가 완성되는 것을 확인 할 수 있다.

사실 이 이외에도 얘기할 거리가 많다. 예로 들면 사용자가 같은 부분을 삭제할 때 어떻게 해결해야 할지도 골치아픈 문제이다. 하지만, 여기까지 읽었으면 대충 동시문서편집 기술이 어떤 방식으로 작동하는지 알 수 있을 것이라고 기대한다. 실제로 이 동시문서편집 기술은 개발자들에게도 쉽지 않은 개념이기 때문에 어려운 것이 당연하다. 이 글을 계기로 동시문서편집 기술에 관심을 갖게 된 사람들은 현재 필자가 동시문서편집 기술을 적용하고 있는 서비스를 같이 한번 제작해보면 어떨까하는 마음으로 해당 작업 url을 남기고 글을 마치려 한다.

카툰 리뷰 동시 편집 서비스: https://github.com/yorkie-team/toonie

참고) CRDT vs OT: https://channel.io/ko/blog/crdt_vs_ot