분산환경에서 Tsid 사용하기

Visitors

대부분 데이터를 구분하기 위해서는 고유한 ID(Primary Key)를 사용하는데,
전통적인 RDB에서는 AUTO_INCREMENT 같은 단일 시퀀스로도 충분했었다.

하지만, 분산환경으로 가면서 서버가 여러 대이고, DB도 여러 개(샤딩)으로 나뉘었을 때
“어떻게 충돌 없이, 순서를 유지하는 고유 ID를 만들 수 있는가”에 대한 내용으로 등장한 것이 TSID 이다.

기존 방식의 한계

1. AUTO_INCREMENT (DB 시퀀스)

  • DB가 여러대로 구성되어 있는 경우 ID 충돌 위험이 있음
  • 샤딩 후에는 각 노드가 ID 범위를 나눠서 사용해야함 -> 관리 복잡

2. UUID

충돌은 거의 없지만,

  • 순서가 보장되지 않는다 -> 인덱스가 파편화 된다
  • 읽기 어렵다 (ex. 550e8400-e29b-41d4-a716-446655440000)

특히, 대규모 트래픽이 예상될 때, INSERT 시 마다 디스크 쓰기의 성능이 급격하게 떨어진다.

2. TSID

TSID(Time-Sortable ID)는 “타임스탬프 기반으로 생성되는, 시간 순으로 정렬 가능한 고유 ID” 이다. 내부구조는 Timestamp(밀리초 단위 시간) + Node ID(서버 또는 샤드 식별자) + Sequence(같은 밀리초 내에서 중복방지) => Long 타입으로 표현 한 형태이다.

  • 시간 순으로 생성되므로 정렬이 쉽다.
  • B-Tree 인덱스 split 최소화 -> INSERT 성능 향상
  • 시간 순서가 중요한 데이터에 적합(특히 이벤트, 메시지, 로그 등)
  • 각 노드(서버/샤드)가 자신의 Node ID로 구분되므로 ID 충돌이 없다

Spring Boot + JPA 에 적용 방법

  • build.gradle.kts 에 추가
      dependencies {
          implementation("io.hypersistence:hypersistence-utils-hibernate-60:3.5.1")
      }
    
  • Entity 에 @Tsid 추가
      @Entity
      @Table
      class User(
          val email: String,
          val nickname: String,
          ...
      ) : Audit() {
          @Id
          @Tsid // 추가
          var id: Long? = null
              protected set
        
          ...
        
      }
    
  • 실제로 Entity 저장시 Image

  • 생성된 Tsid 768729030917065909 을 기준으로 2진수로 표현하면
    0000 1010 1010 1011 0001 0010 1111 1011 0001 0000 0101 0111 1000 1100 1011 0101
    이 되고, 이걸 오른쪽부터 나눠서 보면 아래와 같다.

    구간 비트 수 2진수 10진수 의미
    하위 12비트 12 1100 1011 0101 3253 Sequence
    그 위 10비트 10 0101 1110 1000 376 Node ID
    나머지 42비트 42 0000 1010 1010 1011 0001 0010 1111 1011 0001 0000 0101 183,279,283,265 Timestamp (2020-01-01T00:00:00Z 이후 ms)
  • 참고로 Node ID의 생성 전략에는
    • 설정하지 않으면 랜덤으로 생성(default)이 되므지만, 충돌 가능성을 낮추기 위해서 default 로 사용하지는 말자.
    • Node ID를 지정하는 방법에는 아래와 같은 것들이 있다.
      • 시스템 프로퍼티 또는 환경변수로 지정
        // JVM 인자. node bit수를 자동으로 7bit(100개에 피 ㄹ요한 최소 비트)로 줄여주고, 남은 비트는 시퀀스에 더 사용할 수 있도록 설정.
        JAVA_TOOL_OPTIONS="-Dtsid.node=7 -Dtsid.node.count=100"
              
        // 환경변수로 설정  
        TSID_NODE=123 TSID_NODE_COUNT=100
        
      • 프로그램적으로 제어
        @Configuration
        class TsidConfig(
          @Value("\${tsid.node:-1}") 
          private val node: Int,
                
          @Value("\${tsid.node-bits:10}") 
          private val nodeBits: Int,
        ) {
              
          @Bean
          fun tsidFactory(): TSID.Factory {
           return TSID.Factory.builder()
                    .apply { if (nodeBits in 0..20) withNodeBits(nodeBits) }
                    .apply { if (node >= 0) withNode(node) }
                    .build()
          }
              
        }
        

      더 자세한 내용은 https://github.com/vladmihalcea/hypersistence-tsid 를 참고하자