Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
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
29 30
Archives
Today
Total
관리 메뉴

책을 읽읍시다

외부 시스템의 긴 ID 때문에 터진 장애, 그리고 배운 점 (feat. MySQL, Hash, Index) 본문

카테고리 없음

외부 시스템의 긴 ID 때문에 터진 장애, 그리고 배운 점 (feat. MySQL, Hash, Index)

근면한 거위 2025. 5. 25. 00:37

연동 개발을 하다 보면 외부 시스템에서 전달되는 고유 ID를 받아 저장해야 하는 상황이 자주 생긴다. 그 ID는 서비스 입장에선 중요한 식별자이지만, 형식도, 길이도 우리가 통제할 수 없는 블랙박스다.

 

당연히 문제는 이런 예상 밖의 상황에서 터진다.

예기치 않은 장애의 시작

어느 날, 특정 연동 시스템에서 보내온 고유 ID가 우리 데이터베이스에 저장되면서 중복 데이터가 쌓이기 시작했다.
처음엔 삽입 조건이 이상한 건가 싶었지만, 로그를 뜯어보니 놀라운 사실이 있었다.

외부 시스템이 보낸 긴 문자열 ID가 DB에 저장될 때, 잘려서 저장되고 있었다.

 

이 잘림 현상은 우리가 설정한 컬럼 길이(VARCHAR(50))를 넘어선 ID가 삽입될 때 발생했다. DB는 잘려진 값을 그대로 저장했고, 결과적으로 서로 다른 두 ID가 동일한 값으로 인식되었다. 그러다 보니 중복 삽입이 발생했고, 그 과정에서 AUTO_INCREMENT 값도 불필요하게 빠르게 증가하면서 문제가 확산되었다.

 

왜 이런 일이 발생했을까?

우리는 처음에 "외부 ID면 보통 짧겠지"라는 생각으로 컬럼 길이를  VARCHAR(50)으로 잡아뒀다.
하지만 특정 외부 시스템은 최대 길이 70~100자의 문자열을 식별자로 사용하고 있었다.
문제는 이 ID가 일부는 길고, 일부는 짧기 때문에 정상 동작하는 경우가 많아 문제를 초기에 발견하기 어려웠다는 점이다.

 

응급처치가 아니라, 설계를 다시 해야 했다

우선 당장의 문제를 막기 위해 데이터 삽입 조건을 점검하고 컬럼 길이를 늘리는 조치를 했다.
하지만 이건 근본적인 해결책이 될 수 없었다. 이유는 두 가지였다.

  1. 앞으로 더 긴 값이 들어올 수도 있기 때문이다.
  2. 긴 문자열을 직접 인덱싱하면 성능 문제가 생기기 때문이다.

그래서 우리는 아예 설계 방향을 바꾸기로 했다.

 

현실적인 해결책: "원본은 저장하고, 인덱싱은 해시로"

우리가 선택한 방법은 다음과 같은 구조다:

  1. 원본 ID는 넉넉한 길이로 그대로 저장한다. 예를 들어 VARCHAR(100) 또는 그 이상.
  2. 이 ID를 CRC32나 CRC64 해시로 변환한 값을 별도 컬럼에 저장한다.
  3. 해시 컬럼에 인덱스를 건다.
  4. 조회 시에는 해시만으로 찾는 게 아니라, 다음과 같이 해시 + 원본 비교를 같이 한다
    (이런식으로 충돌을 방지하며 빠르게 검색이 가능하다는 점에 꽤나 감탄했다.)
SELECT *
FROM records
WHERE id_hash = ?
  AND external_id = ?;

 

이 방식은 아래와 같은 장점이 있다:

  • 인덱스가 작고 효율적이다. 해시 값은 고정된 크기의 숫자이기 때문에 인덱스 부하가 크지 않다.
  • 충돌 가능성은 낮고, 충돌이 발생해도 AND external_id = ? 비교로 충분히 검증이 가능하다.
  • 원본 문자열은 손상 없이 저장되므로 어떤 형태든 수용 가능하다.

 

이 경험을 통해 배운 것

이 사건을 겪으며 몇 가지 중요한 교훈을 얻었다:

  • 외부 시스템의 데이터를 믿지 말자. 우리는 그들이 보내는 ID가 "적당할 것"이라고 막연히 생각했지만, 현실은 전혀 그렇지 않았다.
  • 항상 방어적으로 설계하자. 코드 레벨, DB 레벨 둘 다에서 방어적 관점은 필요하다.
  • 긴 문자열을 인덱싱하지 마라. 가능한 한 짧은 정수 또는 고정 길이 해시로 대체하는 것이 성능적으로 훨씬 안정적이다.
  • 완벽한 설계보다, 현실에서 살아남는 설계가 중요하다.

 

다음 이야기

이 문제를 해결하면서 자연스럽게 떠올랐다:

  • 왜 AUTO_INCREMENT를 쓰면 성능이 좋다고 할까?
  • UUID처럼 무작위 값은 왜 성능을 망칠까?
  • 인덱스는 왜 작은 키일수록 좋다고 할까?

이런 고민은 단순히 "DB를 잘 쓰기"보다 시스템을 더 깊이 이해하는 출발점이 된다.

다음 글부터는 이 고민들을 하나씩 풀어보려 한다.