일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- softeer
- JPA
- Test code
- Java
- ObjectMapper
- Test
- JPQL
- 공룡책
- FCM
- enumSet
- 자바
- proxyFactory
- mapstruct
- Spring
- ExceptionResolver
- Service 계층 테스트
- Test Doulbe
- modelmapper
- OS
- 인프콘2023
- 일상
- MySQL
- 테크쇼
- backend
- db
- Server
- 소프티어
- Coputer Science
- RequestBody
- Junit 5
- Today
- Total
공부내용공유
MogoDB ObjectId (feat: java ObjectId) 본문
서론
온보딩 프로젝트를 맡으면서 처음 mongoDB를 사용하게 되어서 정신없이 mongoDB의 기본적인 문법과 spring 에서 제공하는 spring mongo jpa, mongoTemplate 만을 공부하고 디테일한 요소를 챙기지 못했었다.
java에서 Bson을 위해 ObjectId 라는 객체를 지원한다는 것을 모르고 id는 String 으로만 사용했었고, 시간 관련 작업을 할 때 id를 활용하겠다는 생각을 못하다가 나중에야 알게되었다.
이 글에서는 objectId의 개념과 java에서 지원하는 객체에 대한 설명, id를 시간 관련 기능에서 어떻게 사용할 수 있는지 정리할 예정이다.
본론
ObjectId란?
저장되는 ObjectId의 예시를 보면 ObjectId(6629ad81291d943e57b49ea4) 형태나 단순 문자열6629ad81291d943e57b49ea4 이렇게 저장되어 있는걸 볼 수 있다.
해당 문자열은 총 12byte로
- Time Stamp [4byte]
- process random value [5 byte]
- machine [3 byte]
- process [2 byte]
- increment count [3 byte]
로 구성되어져 있다.
사용자 입장에서는 id라면 어느정도 범위에서는 (적어도 한 테이블 안에서는) 유니크함을 기대할 것이다.
ObjectId는 위에 있는 3가지 항목으로 유니크함을 보장해주지만 당연히 깨질 수도 있다, 각 항목별로 어느정도까지 커버가 가능할까?
1. TimeStamp
TimeStamp 의 경우 초 단위까지 저장하기 때문에 1초안에 여러 document가 생긴다면 같은 값을 가질 수 있다.
2. Process random value
process random value도 machine별 로 3 byte의 랜덤 값 과 process 별 로 2 byte의 랜덤 값을 만들기에 같은 값이 나올 수 있긴하다.
3. increment count
마지막 increment count는 각 언어별 driver 마다 구현이 조금씩 다르나 보통 랜덤한 값을 시작으로 증가를 시키면서 값을 사용한다.
조금 예전 스택 오버 플로우 글에서도 볼 수 있듯이 3 byte 이기 때문에 16,777,216 의 개수까지 커버가 가능하다 되어 있다. 즉, objectId가 중복이 되려면 1초안에 한 프로세스 내에서 16,777,216개의 document가 만들어져야 한다. (이론상)
java에서 지원하는 objectId 클래스
java 에서는 ObjectId 클래스를 지원해준다, 해당 클래스에서 사용 가능한 메서드를 몇 개 알아보자.
1. 최종적인 id 문자열은 16진법을 사용하기 때문에 관련된 필드와 메서드를 지원한다.
//ObjectId 클래스 필드
private static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public String toHexString() {
char[] chars = new char[24];
int i = 0;
byte[] var3 = this.toByteArray();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
byte b = var3[var5];
chars[i++] = HEX_CHARS[b >> 4 & 15];
chars[i++] = HEX_CHARS[b & 15];
}
return new String(chars);
}
2. 메서드 이름에서 바로 알 수 있듯이 date를 입력하면 해당 값으로 가장 작은 id를 만들어낸다.
public static ObjectId getSmallestWithDate(Date date) {
return new ObjectId(dateToTimestampSeconds(date), 0, (short)0, 0, false);
}
3. compareTo는 코드에서 볼 수 있듯이 16진수 문자열을 앞에서부터 차례차례 비교를한다.
public int compareTo(ObjectId other) {
if (other == null) {
throw new NullPointerException();
} else {
byte[] byteArray = this.toByteArray();
byte[] otherByteArray = other.toByteArray();
for(int i = 0; i < 12; ++i) {
if (byteArray[i] != otherByteArray[i]) {
return (byteArray[i] & 255) < (otherByteArray[i] & 255) ? -1 : 1;
}
}
return 0;
}
}
TimeStamp 관련 작업에 대하여
서론에서 시간 관련 작업을 사용할 때 id를 사용해도 됐다는 것을 나중에 알게 되었다고 하였다. 애초에 time stamp로 id를 만들고ObjectId 클래스에서도
private static int dateToTimestampSeconds(Date time) {
return (int)(time.getTime() / 1000L);
}
public ObjectId(Date date, int counter) {
this(dateToTimestampSeconds(date), counter, true);
}
public int getTimestamp() {
return this.timestamp;
}
public Date getDate() {
return new Date(((long)this.timestamp & 4294967295L) * 1000L);
}
이러한 다양한 메서드를 제공한다.
그래서 document를 정렬하거나 시간을 기준으로 db에서 가져올 때 create date가 아니라 id를 사용할 수도 있겠구나 라는 생각을 했다.
이를테면 기존 created_at 으로 특정 날짜부터 특정 날짜까지의 아이템을 가져오는 쿼리의 경우
public List<ObjectId> getItemByDate(LocalDateTime start, LocalDateTime end){
query.addCriteria.where(created_at).gt(start);
query.addCriteria.where(created_at).lt(end);
return query.execute();
}
이러한 코드를
public List<ObjectId> getItemByDate(LocalDateTime start, LocalDateTime end){
Object id startId = ObejctId.getSmallestWithDate(start);
Object id endId = ObejctId.getSmallestWithDate(end.plusDay(1));
query.addCriteria.where(id).gt(startId);
query.addCriteria.where(id).lt(endId);
return query.execute();
}
이런식으로 변경이 가능하다, 이 코드가 무조건 좋다는건 아니지만 적어도 내 상황에서는 이렇게 했을 때 인덱스를 좀 더 효율적으로 사용할 수 있었기 때문이다.
하지만 id를 생성 날짜의 기준으로 삼을 수 있나에서 문제가 생길 수 있으니 주의해서 사용해야 한다. 기술적으로는 충분히 가능하지만 예약 구매, 예약 전송 등의 비즈니스 로직이 있다고 하면 해당 기능은 일단 document를 생성하고 나중에 상태값을 바꿔주는 식으로 풀어줄 수도 있다. (내 상황이 그래서 해당 방식을 사용하지 못했다.)
결론
뭐 그래서 결론은 단순 문자열이 아닌 이러한 값들로 만들어지고 ObjectId 클래스 자체에서도 생성 값 관련 많은 메서드를 제공하니 상황에 맞게 그냥 문자열이네 하고 String으로 만들지 않고 ObjectId를 적극 활용하는 것도 좋을 것 같다!
'Server > MongoDB' 카테고리의 다른 글
MongoDB aggregate (feat: spring batch partition) (0) | 2024.10.06 |
---|---|
MongoDB 샤딩 적용하기 (with spring) (0) | 2024.06.22 |
MongoDB 전문 검색 (Full Text Search) (0) | 2024.01.28 |