Foundry
분산 ID 생성기 설계
심화
핵심

정렬이 보장하는 것과 아닌 것

ID 크기로 선후를 판정하면 안 된다

이 방식의 ID는 시간순으로 커집니다. 그래서 정렬을 믿고 쓰기 쉽습니다. 어디까지 믿어도 되는지를 정확히 알아야 합니다.

같은 밀리초 안에서는 노드 번호가 순서를 정한다

같은 밀리초 안에서는 노드 번호가 순서를 정하므로 실제 발급 순서와 다르다 같은 밀리초에 세 노드가 발급했다 노드 9 먼저 노드 2 노드 5 실제 시간 순서 ID 크기 순서는 노드 2, 노드 5, 노드 9. 노드 번호가 순서를 정한다 밀리초 안 순서는 발급 순서와 무관하다 그래서 ID 크기로 두 사건의 선후를 판정하면 안 된다

시각 칸이 같으면 다음 칸인 노드 번호가 크기를 정합니다. 노드 9가 먼저 발급했어도 노드 2의 ID가 더 작습니다.

즉 밀리초 안 순서는 발급 순서와 무관합니다. 요구사항에서 이미 "같은 밀리초 안 순서를 보장하지 않는다" 고 적었고, 이것이 그 뜻입니다.

노드 시계가 다르면 범위가 더 넓어진다

노드마다 시계가 몇 밀리초 다를 수 있습니다. 그러면 그 오차만큼의 구간에서 순서가 뒤바뀝니다.

노드 시계 오차가 5ms 라면
5ms 안에 발급된 ID 들의 크기 순서는 발급 순서와 다를 수 있다

시계를 잘 맞추면 이 창이 좁아지지만 0이 되지는 않습니다. 정렬을 믿어도 되는 단위는 시계 오차보다 커야 합니다.

인과 관계는 보장하지 않는다

A를 만든 뒤 그 결과로 B를 만들었다면 B가 나중입니다. 그런데 두 발급이 다른 노드에서 일어났고 같은 밀리초 안이거나 시계가 어긋나 있으면 B의 ID가 더 작을 수 있습니다.

쓰임안전한가
목록을 대략 시간순으로 보여주기안전하다
같은 노드가 만든 두 ID의 선후안전하다
서로 다른 노드의 두 ID의 선후안전하지 않다
인과 관계 판정안전하지 않다

선후가 정말 중요하면 그 사실을 따로 기록해야 합니다. ID의 크기에 기대면 드물게 뒤집히고, 그 드문 경우가 정확히 문제가 되는 순간입니다.

ID를 페이징 기준으로 쓸 때

목록을 ID 순으로 페이징하는 것은 흔한 패턴입니다. "마지막으로 본 ID 다음부터" 로 요청합니다.

여기에 함정이 있습니다. 시계가 뒤로 갔거나 노드 사이 오차가 있으면 이미 지나간 위치보다 작은 ID가 나중에 삽입될 수 있습니다. 그 항목은 페이징에서 건너뛰어집니다.

상황결과
삽입이 늘 커지는 값으로누락이 없다
작은 값이 뒤늦게 삽입그 항목을 못 본다

빠진 것은 눈에 보이지 않습니다. 중복은 사용자가 알아채지만 누락은 아무도 모릅니다.

그래서 무엇을 하나

요구방법
대략 시간순 목록ID 정렬로 충분하다
빠짐없는 순회별도의 증가 값이나 시간 범위 조회를 쓴다
선후 판정ID 가 아니라 명시적인 순서 정보를 쓴다

정렬은 저장 비용을 위해 도입한 성질이고 논리의 근거로 쓰라고 만든 것이 아닙니다. 이 구분을 지키면 뒤에서 생기는 문제가 크게 줄어듭니다.

면접에서 이렇게 나옵니다

Q.이 ID의 정렬은 어디까지 믿을 수 있나요

시계 오차보다 큰 단위에서만 믿습니다.

같은 밀리초 안에서는 다음 칸인 노드 번호가 크기를 정합니다. 노드 9가 먼저 발급해도 노드 2의 ID가 더 작습니다.

노드마다 시계가 몇 밀리초 다를 수 있으므로 그 오차만큼의 구간에서 순서가 뒤바뀝니다.

쓰임안전한가
대략 시간순 목록안전하다
같은 노드의 두 ID 선후안전하다
다른 노드의 두 ID 선후안전하지 않다

흔한 실수: "시간순 정렬" 을 순서 보장으로 이해하는 것. 요구사항에 "같은 밀리초 안 순서를 보장하지 않는다" 고 적은 이유가 이것이고, 그 문장이 실제로 무엇을 뜻하는지 말할 수 있어야 합니다.

Q.ID 크기로 두 사건의 선후를 판정해도 되나요

안 됩니다. 인과 관계는 보장하지 않습니다.

A를 만든 뒤 그 결과로 B를 만들었어도, 두 발급이 다른 노드에서 일어났고 같은 밀리초 안이거나 시계가 어긋나 있으면 B의 ID가 더 작을 수 있습니다.

선후가 정말 중요하면 그 사실을 따로 기록한다
ID 크기에 기대면 드물게 뒤집힌다

그리고 그 드문 경우가 정확히 문제가 되는 순간입니다. 평소에는 맞으므로 검증에서 드러나지 않고 운영에서 드러납니다.

흔한 실수: 시계를 잘 맞추면 된다고 답하는 것. 오차가 좁아질 뿐 0이 되지 않고, 같은 밀리초 안 순서는 애초에 보장하지 않기로 정한 것입니다.

Q.ID를 페이징 기준으로 쓸 때 무엇을 주의하나요

작은 ID가 뒤늦게 삽입되면 그 항목을 건너뜁니다.

"마지막으로 본 ID 다음부터" 로 요청하는 패턴에서, 시계가 뒤로 갔거나 노드 사이 오차가 있으면 이미 지나간 위치보다 작은 ID가 나중에 들어올 수 있습니다.

상황결과
삽입이 늘 커지는 값으로누락이 없다
작은 값이 뒤늦게 삽입그 항목을 못 본다

빠진 것은 눈에 보이지 않습니다. 중복은 사용자가 알아채지만 누락은 아무도 모릅니다.

흔한 실수: 이 위험을 알고도 그대로 쓰는 것. 화면 목록은 괜찮지만 데이터를 옮기거나 집계하는 순회에서는 누락이 곧 사고입니다. 그런 용도에는 별도의 증가 값이나 시간 범위 조회를 씁니다.

Q.정렬이 필요한 곳과 순서가 필요한 곳을 어떻게 구분하나요

정렬은 저장 비용을 위해 도입한 성질이고 논리의 근거가 아닙니다.

요구방법
대략 시간순 목록ID 정렬로 충분하다
빠짐없는 순회별도의 증가 값이나 시간 범위 조회
선후 판정명시적인 순서 정보

처음에 정렬을 요구사항에 넣은 이유가 인덱스 삽입 비용이었다는 것을 기억하면 구분이 쉽습니다. 그 목적에는 완벽하게 맞고, 다른 목적에는 맞지 않습니다.

흔한 실수: 하나의 값이 여러 목적을 겸하게 만드는 것. ID가 식별자이면서 순서이면서 시각이면 편해 보이지만, 어느 목적의 보장이 어디까지인지 아무도 모르게 됩니다. 목적마다 필요한 보장을 따로 적는 편이 안전합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

분산 ID 생성기 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.