스프링 부트로 블로그 서비스 개발하기

포스트 CRUD 기능 - Snowflake로 게시글 id 만들기

exena 2026. 1. 7. 21:32

snowflake는 트위터에서 만든 알고리즘인데, MSA(분산 시스템)에서 오름차순 ID를 만들기 위한 알고리즘이다.

다만 라이브러리가 아니라 그냥 알고리즘이라서 우리가 코드를 작성해야 한다.

public class Snowflake {
    private static final int UNUSED_BITS = 1;
    private static final int EPOCH_BITS = 41;
    private static final int NODE_ID_BITS = 10;
    private static final int SEQUENCE_BITS = 12;

    private static final long maxNodeId = (1L << NODE_ID_BITS) - 1;
    private static final long maxSequence = (1L << SEQUENCE_BITS) - 1;

    private final long nodeId = RandomGenerator.getDefault().nextLong(maxNodeId + 1);
    // UTC = 2024-01-01T00:00:00Z
    private final long startTimeMillis = 1704067200000L;

    private long lastTimeMillis = startTimeMillis;
    private long sequence = 0L;

    public synchronized long nextId() {
       long currentTimeMillis = System.currentTimeMillis();

       if (currentTimeMillis < lastTimeMillis) {
          throw new IllegalStateException("Invalid Time");
       }

       if (currentTimeMillis == lastTimeMillis) {
          sequence = (sequence + 1) & maxSequence;
          if (sequence == 0) {
             currentTimeMillis = waitNextMillis(currentTimeMillis);
          }
       } else {
          sequence = 0;
       }

       lastTimeMillis = currentTimeMillis;

       return ((currentTimeMillis - startTimeMillis) << (NODE_ID_BITS + SEQUENCE_BITS))
          | (nodeId << SEQUENCE_BITS)
          | sequence;
    }

    private long waitNextMillis(long currentTimestamp) {
       while (currentTimestamp <= lastTimeMillis) {
          currentTimestamp = System.currentTimeMillis();
       }
       return currentTimestamp;
    }
}

노드 ID와 시퀀스 번호, 타임스탬프를 조합해서 64비트 ID를 만들어내는 코드이다.

이 클래스를 모듈로 만들어서 사용할 것이다.

 

외부에서 이 모듈을 사용하려면 이렇게 라이브러리처럼 넣어주면 된다.

그러고보니 쿠팡 해킹 사태가 터지고 나서 유저 ID를 그냥 AUTO_INCREMENT로 했다느니 하는 소문이 있었는데, 사실 AUTO_INCREMENT를 쓰더라도 외부에 노출하지 않고 내부적으로만 사용하면 괜찮다. UUID를 쓴다면 외부에 노출해도 그냥 난수이기 때문에 큰 정보 노출은 없을 것이다. 하지만 정렬이 안되기 때문에 성능 문제가 있다. UUIDv7은 정렬이 되지만 용량이 너무 크다(128bit).

Snowflake나 TSID와 같은 유니크 정렬 숫자를 쓰면 성능 문제와 보안 문제를 둘 다 해결할 수 있다.

나는 IdGenerator 포트를 통해서 Snowflake를 사용하는 빈을 주입받도록 만들었는데(클린코드 방식)

그냥 바로 서비스단에 넣어서 써도 상관없다.

public Article publishArticle(String username, PublishArticleFormRequest request){
    Member member = memberRepository.findByEmail(new Email(username))
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
    Article article = Article.of(idGenerator.nextId(), request, member);
    articleRepository.save(article);
    return article;
}

서비스단의 publishArticle 함수에서 id를 만들어서 Article 객체의 생성자에 넣는다.

원래는 Article 객체에서 id가 null인 채로 DB에 들어가면 @GeneratedValue로 생성하는 방식이었는데 이제 해당 어노테이션은 지워줬다.