대규모 분산시스템의 Unique ID
데이터의 Unique ID는 굉장히 기초적이고 간단한 것 같으면서도, 파고들면 꽤나 어려운 문제이기도 하다.
중소규모 시스템 (RDB)
일반적인 규모의 시스템에서는 사실 관계형 데이터베이스에서 제공하는 기본 옵션을 사용해도 무방하다.
MySQL의 auto increment나, Oracle/PostgreSQL의 sequence와 같은 것들이 그것이다.
하지만 입력이 초당 수천건, 수만건을 넘어가고 DB를 다중화하는 대규모 시스템으로 나아가면 이런 것들로는 버티기 어려워진다.
표준 UUID
uuid v4는 고유한 ID를 만들기 위한 문자열 포맷이다.
시간과 난수값을 기반으로 랜덤한 문자열을 만든다.
32개의 16진수로 표기되며, 16바이트의 크기를 가진다. 이런 형태다.
550e8400-e29b-41d4-a716-446655440000
충돌 가능성이 없지는 않지만, 매우 희박해서 분산된 시스템 간에도 ID를 마구 뽑아내서 사용하기 좋다.
단점은 크기가 16바이트로 꽤나 크고, 시간순 정렬이 안된다는 것이다.
그런걸 제외하면 꽤 좋은 방법이고, RDB 등에서도 종종 사용하는 기법이기도 하다.
변형 UUID (MongoDB 등)
NoSQL의 대표주자 중 하나인 몽고DB는 대규모 분산시스템을 타겟으로 만들어진 데이터베이스 시스템이다.
그래서 Unique ID 생성법도 그에 맞추어져있는데, 랜덤으로 생성된 UUID를 키로 삼는다. 다만, uuidv4가 아니라 자체 구현한 12바이트의 id 구조를 사용한다는 것이다.
이건 표준 uuid와는 다르게 시간순 정렬이 되고, 크기도 비교적 작다!
그리고 mysql의 경우에는 uuidv4에 시간순 정렬이 가능한 보조기능을 제공한다. uuid_to_bin의 2번째 파라미터에 1을 전달해서 "시간"에 대한 값을 최상위로 옮기는 것이다.
다른 데이터베이스를 사용하더라도 비슷한 방법론을 사용해서 대체가 가능하다.
시간순 정렬도 가능하다는 점에서 uuidv4보다 낫다.
티켓 서버(Ticket Server)
티켓 서버는 꽤나 단순하면서도 강력한 해결방식이라고 할 수 있다.
그냥 auto_increment 역할만 하는 서버를 하나 만들어서 UNIQUE ID를 발급하는 기능을 만들어주고,
다른 서버들이 그걸로 다 받아와서 쓰게 하는 것이다!
야후의 하위서비스인 Flickr가 이런 방식을 사용하고 있다고 한다.
단점은.. Ticket Server가 단일 실패지점(SPOF)이 된다는 것이다.
티켓서버가 죽지 않게 잘 관리해줘야 하고, 부하를 감당할 수 있게 스펙을 잘 맞춰줘야 한다.
스노플레이크(Snowflake) 패턴
이건 정말 초-대규모 분산시스템에서 사용할 수 있는 방식이다.
트위터에서 제안했고, 실제로 사용하고 있는 방법이다. 디스코드에서도 쓴다고 한다.
이런 곳은 대체로 데이터센터도 여러개고, 서버도 여러대다.
- 자체 타임스탬프와
- 데이터센터의 식별번호,
- 데이터센터 안의 서버의 식별번호,
- 그리고 각 서버에서 서버 단위로 관리하는 숫자를 조합해서 다음과 같이 64비트 데이터를 만들어 사용한다.
맨 앞에는 부호비트를 뒀는데, 큰 쓰임새가 있지는 않다. 혹시나 싶어서 넣어뒀다고 한다.
여튼 이런식으로 만들면 만드는 것 자체도 빠르게 만들 수 있고, 상위 비트가 타임스탬프로 되어있기 때문에 시간순 정렬도 쉽게 할 수 있다.
일반적인 규모의 시스템은 데이터센터가 따로 나뉘어있지는 않으니, 데이터센터ID와 서버ID를 통합해서 서버 ID만 둬도 충분할 것이다.
단점은 타임스탬프의 크기다. 저기서는 41비트만큼의 영역만 차지하는데, 저걸로는 최대 69년까지의 시간만 표현할 수 있다. 더 길게하려면 타임스탬프 영역을 더 크게 잡아야 한다.
참조
"가상 면접 사례로 배우는 대규모 시스템 설계 기초", 알렉스 쉬, 인사이트(2021)
https://discord.com/blog/how-discord-stores-billions-of-messages