티스토리 뷰
개요
고유한 객체의 ID를 생성할 일이 생겼다.
그동안 ID를 만들 때, UUID나 랜덤 값으로 ID를 만들었으나 이번에는 제대로 설계를 해보려고 한다. 대부분의 경우에는 UUID로 id를 만들어도 고유성 측면에서는 큰 문제는 없지만, 그냥 사용하기엔 조금 아쉽다.
그래서 가장 많이 레퍼런스로 사용되는 트위터에서 사용되는 id 생성 기법인 스노우플레이크다. 스노우플레이크는 id가 정수로만 표현되서 다루기 쉽고 인덱스 효율성을 보장하지만, id만 가지고 무언가를 식별하긴 어렵다.
이리저리 고민하다가 새로 설계해서 만들어보기로 했다. 이번 포스팅은 설계 과정에서의 의사결정 과정을 정리해봤다.
ID 생성에서 고려할 점들
토스페이먼츠에서 거의 그대로 긁어옴
고유성 : 동일한 시스템 또는 전체 네트워크 내에서 두 개 이상의 객체가 동일한 ID를 갖지 않는 것을 뜻함.
식별 가능성 : 명확한 네이밍, 일관된 구조로 사용자가 예측할 수 있는 방식으로 객체 ID를 만드는 것을 뜻함.
보안성 : 객체 ID는 사용자, 세션, 데이터 등을 고유하게 식별하기 때문에 악의적인 사용자가 이러한 ID를 추측하거나 탈취하여 부적절하게 사용하는 것을 방지
보안성은 다른 곳에서 충분히 보장해주고 있기 때문에, 이번 ID 생성은 고유성과 식별 가능성에 초점을 맞추려고 한다.
UUID (Universally Unique Identifier)
uuid는 1980년대부터 쌓아온 역사가 있는만큼 고유성을 보장하기위해 다양한 노력을 했고, 고유성만큼은 확실하게 보장해준다. 역사가 있는 만큼 UUID는 여러 버전이 존재한다.(커스텀을 포함한다면 v8까지 있다)
자바에선 Java 5부터 UUID를 라이브러리로 제공하고, UUID v4을 사용하고 있다.
public class UUIDExample {
public static void main(String[] args) {
UUID uuid = UUID.randomUUID(); // UUID v4 (랜덤 기반)
System.out.println("Generated UUID v4: " + uuid);
}
}
자바에서 UUID는 라이브러리를 이용한 개발이기 때문에 개발이 간편하다는 장점이 있다. 그리고 UUID v4는 완전한 랜덤값을 기반으로 만들어지기 때문에 조 단위의 데이터를 쌓아야 겨우 중복이 하나 생길까 말까하는 확실한 고유성을 보장한다.
고유성을 보장한다고 단점이 없을까? 그렇지 않다.
UUID의 단점으로는 크게 두 가지가 있다.
1. 키의 길이가 너무 길다.
UUID는 하이픈을 포함해서 36자, 하이픈 제외 32자, 16바이트를 사용한다. 무엇보다 랜덤값을 기반한 랜덤 문자라 가독성이 떨어진다. 다만, 가독성은 서비스의 규모가 작다면(운영 난이도가 낮을수록) 그렇게 고려하지 않아도 될 문제다.
2. 정렬이 불가능하다.
UUID v4에서는 ID 값들이 랜덤으로 생성된다. 그렇다는건 ID가 일관성이 없고, 순차적이지 않다. 이럴 경우 ID가 생성될 때마다 B-Tree기반의 index 구조에서 트리 사이사이에 ID 값이 추가된다. 이럴 경우 index가 매번 재조정되는 문제(index rebalancing 문제)가 발생해 DB 성능이 저하된다. 데이터의 규모가 커질수록 이 문제는 더 커질 것이다. 때문에 서비스의 확장 가능성을 생각한다면, UUID 방식을 선뜻 선택하기 어렵다.
그래서 UUID도 타임스탬프 기반의 v6,v7 를 사용한다면 2번 문제가 발생하지 않는다. 그러나 내가 사용하는 JVM 환경에서 v6를 사용하려면 별도의 라이브러리 추가가 필요하고.. 이럴 바엔 차라리 만들자 생각했다.
ULID (Universally Unique Lexicographically Sortable Identifier)
ULID는 UUID v4랑 다르게 타임스탬프 기반이라 정렬 가능하고, 짧은 장점이 있다. ID 길이가 짧아서 가독성이 장점이라고는 하는데 식별 가능하진 않다.
01H3K1VJ7E2A9Q0XFAZ8YK1V7M
위와 같은 상대적으로 UUID보다는 짧은 형태를 가진다.
스노우 플레이크(Twitter Snowflake)
가상 면접 사례로 배우는 대규모 시스템 설계 책에 나와서 그런지 ID 생성사례로 가장 많이 사용된다. 트위터에서 개발한 분산 시스템에서 고유 ID를 생성하는 방식으로, 모두 정수로 표현된다는 특징이 있다.
분산시스템에서 사용하기위함이라 타임스탬프의 중복을 방지하면서 ID의 고유성을 보장하기위해 worker ID가 들어가는 것이 대표적인 특징이다. UUID의 정렬 불가능한 문제와 길이 문제까지 모두 해결했으며 정수로 표현된다는 장점이 있어서, 많은 곳에서 ID 생성 기법의 예시로 사용한다.
1469154741638678528 // 스노우플레이크 ID의 예시
사실 고유한 ID 생성만이 목적이라면, 스노우플레이크 방식 그대로 채택해도 별 문제가 없을 것이다. 트위터에서 사용했다고하면 신뢰도는 보장이 되며, 먼저 만들어본 사람도 많기 때문에 자료를 찾기 쉽기 때문에 구현도 크게 어렵지 않기 때문이다.
하지만 개인적으로 아쉬운 부분이 있어서 이것저것 자료를 더 찾아보다가 라인에서 채택한 방식(데이터 센터와 서버 ID를 추가 함. 다양한 이슈에 관한 레퍼런스가 인상 깊었음), 인스타그램에서 채택한 방식(샤드 id만을 사용해 분산환경에서도 단순한 방식을 추구함), 각종 DB에서 auto increment 키를 생성하는 방식을 알게됐다.
그러다 토스페이먼츠에서 작성한 좋은 객체 ID(Object ID) 만들기를 보게됐고, 식별 가능성에 대한 생각을 하게되면서 이럴거면 내가 필요한대로 만들어보자는 생각을 하게 됐다.
내가 만들어볼까?
당연하지만 ID는 고유성을 보장해야한다. 그러면서도 식별 가능할수록 좋다. 그리고 보안성을 위해서 대부분의 값들을 인코딩했다.
1. 고유성은 반드시 보장되어야 한다.
일단 최소한의 고유성은 타임스탬프를 이용함으로써 보장한다. 그리고 현재 ECS를 이용 중인데, 이 때 서버에서 생성된 키의 순서를 인코딩해서 다중 서버 환경에서도 고유성을 보장할 수 있도록 했다. 사실 가장 좋은 방법은 DB를 한번 찔러서 auto incrment 키를 가져오는 방식인데, ID를 생성할 때마다 조회하는건 별로 좋은 방식이 아니라고 생각해 서버에서 생성되는 ID수를 체크해 이 시퀀스를 이용해서 ID를 생성하도록 했다.
2. 식별 가능한게 많을수록 좋다.
개발자 입장에서 그동안 다뤄왔던 키의 가장 큰 문제는 어디서 어떻게 만들어졌는지 알 수 없다는 것이다. 때문에 약간의 식별자들을 추가해서 구분하기 쉽게 가독성을 부여했다. 테이블의 prefix와 현재 사용하고 있는 스프링 프로필의 앞자리를 따서 맨 앞에 추가했다. 다만 이렇게되면 ID의 맨 앞자리를 아무나 식별할 수 있게 되는데, 복잡성을 부여하기 위해서 처리할 수 있는 방법이 있는지, 그리고 추가할 값이 있는지 생각을 해볼 필요가 있다.
결과적으로 ID는 아래와 같이 표현된다.
[테이블 Prefix] + [스프링 프로필 앞자리] - [타임스탬프 인코딩] - [ECS 서버 ID + 서버 시퀀스 값 인코딩]
생성된 ID : tabd-3xZ9w7TQ-Z92T4Y
아쉬운점은 식별 가능성을 높이기 위해 일부 알파벳으로 표현되는데, 이럴경우 ID의 크기가 커지는 단점이 있다. 가독성과 효율성은 같이가기 어렵기 때문에 약간의 효율성을 희생했다. 그래도 ID가 고정적인 길이가 나오게끔 했기 때문에, 관리 차원에서는 용이해진다.
이 방법이 최선이라곤 생각하진 않지만 나름대로 우리한테 필요한 부분을 잘 선택해서 만들게 되었다. 더 필요한 부분들이 있으면 계속해서 고민하고 보완할 계획이다.
마치며
완벽하다곤 못하지만 현재 개발 상황에 맞는 ID가 만들어진 것 같아서 나름대로 만족한다.
조금 더 보안성이 있으면 좋겠지만, 현재 만들고 있는 ID는 고유성과 식별성이 조금 더 중요해서 그 부분에 집중해봤다. 아직은 dev 단계니까 개발 과정에서 레퍼런스를 찾고, 성능적인 이슈가 있다면 그 부분에 집중해서 조금 더 발전시킬 수 있을만한 정보를 찾아서 보완할 것이다.
참고자료
https://charsyam.wordpress.com/2012/12/26/%EC%9E%85-%EA%B0%9C%EB%B0%9C-global-unique-object-id-%EC%83%9D%EC%84%B1-%EB%B0%A9%EB%B2%95%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC/
https://www.tosspayments.com/blog/articles/dev-6
https://dev.to/stripe/designing-apis-for-humans-object-ids-3o5a
https://www.mimul.com/blog/id-generation-in-mysql
https://interconnection.tistory.com/25
https://docs.tosspayments.com/resources/glossary/uuid
https://medium.com/@anderson.buenogod/uuid-v7-vs-uuid-v8-choosing-the-ideal-identifier-for-scalable-distributed-system-fa8efc0550f7https://docs.tosspayments.com/resources/glossary/uuid
'개발 > 개발팁' 카테고리의 다른 글
AWS CloudFront + S3 에서 발생한 CORS 문제 해결하기 (1) | 2024.10.21 |
---|---|
월드 ID(world coin)로 인증하기 with. SpringBoot (1) | 2024.03.30 |
Windows 10에서 Phython requirements.txt 자동 생성하기(파이썬 패키지 의존성 자동 생성) (0) | 2024.02.19 |
Windows 10에서 WSL1을 WSL2로 업데이트하기 (1) | 2024.02.10 |
SpringBoot에서 Google Indexing API 사용하기 1 (1) | 2024.02.02 |
- Total
- Today
- Yesterday
- 후쿠오카
- Kotlin
- 람다
- AWS EC2
- JWT
- 티스토리챌린지
- EKS
- lambda
- docker
- OpenFeign
- serverless
- elasticsearch
- ChatGPT
- cache
- 오블완
- openAI API
- MySQL
- S3
- terraform
- springboot
- AWS
- Log
- GIT
- Spring
- 스프링부트
- java
- AOP
- Elastic cloud
- CloudFront
- OpenAI
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |