만든 도구공개

프로토콜

양방향 동기화에서 충돌 사본이 안 생기는 이유는 폴더마다 쓰는 쪽이 하나이기 때문입니다.

writer 1

양쪽이 같은 폴더를 쓰는데 왜 충돌이 안 나는가. 답은 «폴더마다 쓰는 쪽을 하나로» 정했기 때문입니다.

충돌 사본이 왜 생기는가

자동 동기화 폴더를 써 보신 분은 「어떤 파일 (충돌본)」 같은 게 생기는 걸 보셨을 겁니다.

원인은 하나입니다. 두 컴퓨터가 «같은 파일»을 «동시에» 고친 것입니다.

A 컴퓨터 B 컴퓨터 결과
파일을 고침 같은 파일을 고침 어느 쪽이 맞는지 동기화 도구가 모름 → 둘 다 남김

그러면 어느 게 최신인지 사람이 골라야 합니다. 자동화가 안 됩니다.

그래서 그 상황이 «생길 수 없게» 만들었습니다

고치는 게 아니라 애초에 안 생기게 했습니다. 폴더를 갈라서 각 폴더에 쓰는 쪽을 하나로 정했습니다.

폴더 쓰는 쪽 읽는 쪽
받은 편지함 보내는 사람들 주인
보낸 편지함 주인만 보냈던 사람들
기록 주인만 누구나

쓰는 쪽이 하나면 「동시에 고침」이 성립할 수가 없습니다.

읽는 건 여럿이 해도 충돌이 안 납니다. 읽기는 아무것도 안 바꾸니까요.

충돌을 «잘 처리하는» 것보다 «생길 수 없게 만드는» 편이 훨씬 쌉니다.

반쯤 쓰다 만 파일이 건너가면 안 됩니다

두 번째 문제입니다.

파일을 쓰는 데는 시간이 걸립니다. 그런데 동기화 도구는 쓰는 도중에도 파일을 발견할 수 있습니다. 그러면 반쯤 쓰인 파일이 건너가고, 받는 쪽이 그걸 읽다 깨집니다.

해법은 간단합니다.

  1. 임시 이름으로 다 씁니다 — 동기화 도구가 무시하는 이름으로
  2. 다 쓰고 나서 이름을 바꿉니다

이름 바꾸기는 한순간에 끝납니다. 그래서 동기화 도구가 그 파일을 발견하는 시점에는 이미 완성된 파일입니다.

식별 번호가 파일 이름과 같습니다

작은데 편한 설계입니다.

요청의 식별 번호를 파일 이름과 똑같이 만들었고, 그 안에 보낸 사람 이름도 들어 있습니다.

그래서 폴더를 눈으로 보기만 해도 누가 뭘 보냈는지 압니다. 파일을 열어 볼 필요가 없습니다.

「침묵」과 「일하는 중」을 구분합니다

이게 이 방식의 제일 어려운 문제였습니다.

메신저는 오류가 나면 오류가 보입니다. 이 방식은 다릅니다.

비동기 채널은 오류가 아니라 «침묵»으로 고장 납니다.

그리고 침묵은 「아직 일하는 중」과 구분이 안 됩니다. 둘 다 아무 일도 안 일어나는 것으로 보입니다.

실제 보이는 것
열심히 일하는 중 아무것도 안 옴
감시자가 죽음 아무것도 안 옴
파일을 못 읽음 아무것도 안 옴

그래서 「받았다」를 먼저 알리게 했습니다. 일이 끝나기 전에 「접수했고 시작한다」를 보냅니다.

그러면 침묵이 두 종류로 갈립니다 — 접수 표시가 온 침묵(일하는 중)과 접수 표시도 안 온 침묵(뭔가 고장).

찾은 버그 — 「앞줄이 막아서 뒷줄이 영원히 못 들어감」

여기가 값을 치른 자리입니다.

한 번에 처리할 개수를 제한하는 코드가 있었습니다. 「한 번에 5개까지만」 같은 식입니다.

그런데 그 제한이 이미 답한 요청을 걸러 내기 «전»에 걸려 있었습니다.

무슨 일이 생기냐면 —

  1. 폴더에서 요청을 읽음
  2. 먼저 5개로 자름 ← 여기가 문제
  3. 그중 이미 답한 것을 걸러 냄
  4. 남은 게 없음 → 아무것도 처리 안 함

이미 답한 요청 5개가 앞에 있으면, 그 뒤에 있는 새 요청은 영원히 차례가 안 옵니다. 5개를 자르고 나면 전부 걸러지니까요.

그리고 아무 오류도 안 납니다. 그냥 조용히 아무것도 안 합니다.

고치는 건 순서를 바꾸는 것뿐이었습니다 — 거르고 나서 자릅니다.

→ 같은 두 동작인데 순서가 바뀌면 결과가 완전히 달라지고, 그 차이가 화면에 안 나타납니다.

여기부터는 자세한 기록입니다

파일 두 개가 전부다. 요청 하나, 응답 하나. 그 위에 규약이 몇 개 얹혀 있다.

폴더마다 쓰는 쪽이 하나다

양방향 동기화는 두 기계가 같은 파일을 고칠 때 충돌 사본을 만든다. 그래서 그 상황이 애초에 안 생기게 폴더를 갈랐다.

폴더 쓰는 쪽 읽는 쪽
<상대>/inbox 보내는 사람들 상대
<상대>/outbox 상대만 보낸 사람들
<상대>/log 상대만 누구나

어느 파일도 두 기계가 같이 고치지 않는다. 그래서 충돌 해결 로직이 필요 없다 — 충돌이 안 나는 게 아니라 날 수 없는 구조다.

폴더 이름은 실행하는 쪽을 가리킨다. home/inbox는 집 PC가 처리할 것들이다. 처음엔 “보낸 사람 기준”으로 이름을 붙였다가 헷갈려서 뒤집었다 — 파일을 보는 순간 “누가 이걸 실행하나”가 궁금하지 “누가 보냈나”가 궁금한 게 아니었다.

반쯤 쓰인 파일은 복제되지 않는다

파일을 임시 이름으로 다 쓴 뒤 이름을 바꾼다. 동기화 도구가 잡아가는 시점에 이미 완성된 파일이라는 뜻이다.

이걸 안 하면 반쯤 쓰인 JSON이 건너가고, 반대편은 그걸 파싱하다 실패한다. 그리고 그 실패는 재현이 거의 안 된다 — 파일 크기와 동기화 타이밍이 맞아떨어질 때만 나기 때문이다.

식별자가 파일명과 같다

요청 식별자는 항상 파일명과 같고, 그 안에 보낸 사람 이름이 들어 있다.

그래서 두 기계가 같은 inbox에 써도 이름이 부딪히지 않는다. 그리고 파일 목록만 봐도 누가 뭘 언제 보냈는지 읽힌다 — 디렉터리 목록 자체가 로그가 된다.

식별자를 따로 만들고 파일명을 순번으로 두는 방법도 있었는데, 그러면 파일을 열어 봐야 뭐가 뭔지 안다.

침묵과 작업 중을 구분한다

비동기 채널은 에러가 아니라 침묵으로 고장 난다. 그리고 침묵은 “아직 일하는 중”과 구분되지 않는다.

그래서 감시자는 잡을 집는 그 순간에 상태를 running으로 써서 내보낸다. 끝나면 덮어쓴다.

이 한 줄 때문에 “아직 못 봤다”와 “지금 하는 중”이 갈린다. 답이 안 왔을 때 물어볼 질문이 달라진다 — 전자면 동기화를 의심하고, 후자면 그냥 기다리면 된다.

찾은 버그 — head-of-line starvation

한 번에 처리할 개수를 자르는 코드가 이미 답한 요청을 걸러내기 전에 실행되고 있었다.

그래서 처리는 끝났지만 안 치운 요청 3건이 큐 앞에 있으면 새 작업은 영원히 집히지 않는다. 에러 없음. 감시자는 계속 돈다. 로그도 정상이다.

스모크 테스트에서 네 번째 작업이 처리되지 않는 걸로 잡았다. 세 개까지는 전부 통과했으니, 세 개만 던졌으면 못 봤다.

상한이 3이라 네 번째에서 걸린 건데, 이건 운이었다. 상한이 10이었으면 열한 번째에서 걸렸을 테고, 테스트를 열한 개까지 던져 볼 이유가 없다.