pile·
DB / 데이터·마켓컬리마켓컬리 Hello World·

JPA 덕분에 DB에서 삽질한 이야기

JPA 엔티티의 UUID를 MySQL BINARY(255)에 저장한 뒤 같은 UUID로 조회하지 못한 문제를 재현하고 원인을 좁혀 간다. H2와 개발용 MySQL의 테스트 결과가 다르다는 단서에서 출발해, UUID의 실제 크기와 MySQL BINARY 타입의 오른쪽 패딩 규칙을 확인한다. BINARY(16)으로 스키마를 맞추는 것이 단순한 우회가 아니라 데이터 표현과 조회 조건을 일치시키는 해결책임을 SQL로 검증한다.

핵심 포인트
  • UUID 기본 키를 저장했지만 findById가 값을 찾지 못했고, H2 기반 테스트와 MySQL 기반 테스트가 서로 다른 결과를 냈다.
  • UUID는 16바이트인데 컬럼이 BINARY(255)여서 MySQL이 나머지 공간을 오른쪽 패딩 값으로 채웠다.
  • Hibernate는 UUID를 기본적으로 binary로 저장하므로 엔티티 매핑과 실제 컬럼 길이의 불일치가 조회 실패로 이어졌다.
  • @Column(columnDefinition = "BINARY(16)")으로 길이를 명시하자 애플리케이션 조회가 정상화됐다.
  • 직접 만든 테이블과 SQL로 패딩 포함 여부, 16바이트 조회 실패, RPAD를 넣은 조회 성공을 차례로 재현했다.
상세 정리
  • 증상: UUID를 ID로 쓰는 엔티티를 먼저 저장하고 다른 기능에서 같은 UUID로 조회했지만 Repository가 null을 반환해 예외가 발생했다.
  • 재현 전략: 기능별 테스트 컨텍스트를 비교했다. 저장 기능은 H2 인메모리 DB에 의존했고 조회 기능은 개발 MySQL에 의존한다는 차이를 발견했다.
  • 초기 가설: UUID 하나는 16바이트라는 자료와 실제 스키마를 대조하니 컬럼이 BINARY(255)로 생성돼 있었다.
  • 첫 수정: 엔티티 ID 필드에 BINARY(16)을 명시했다. 동일한 조회 테스트가 통과해 컬럼 길이가 문제와 연결돼 있음을 확인했다.
  • 추가 의문: H2와 MySQL의 binary 처리 차이로만 결론내리지 않고, MySQL 공식 문서에서 BINARY 값이 지정 길이까지 오른쪽 패딩된다는 규칙을 찾았다.
  • 매핑 근거: RFC 4122에서 UUID 크기가 16바이트임을 확인했고, Hibernate가 UUID의 기본 표현으로 binary를 사용한다는 가이드도 대조했다.
  • 독립 재현: BINARY(255) 기본 키를 가진 temp 테이블을 만들고 MySQL uuid()로 테스트 값을 생성했다.
  • 바이트 확인: UUID 문자열에서 하이픈을 제거하고 UNHEX한 값의 length를 조회해 실제 길이가 16임을 확인했다.
  • 저장 확인: 16바이트 값을 BINARY(255)에 넣은 뒤 length(id)와 hex(id)를 조회하자 남은 영역에 패딩 값이 들어갔다.
  • 실패 검증: 원래의 16바이트 UNHEX 값만 조건으로 사용하면 저장된 행을 찾지 못했다. 애플리케이션에서 나타난 증상과 동일하다.
  • 성공 검증: 조회 조건도 RPAD로 255바이트까지 채우자 행을 찾았다. UUID 자체가 잘못된 것이 아니라 고정 길이 컬럼의 저장 표현이 달랐던 것이다.
  • 결론: UUID binary 컬럼은 실제 16바이트 크기에 맞춰 선언해야 한다. 테스트 DB가 운영 DBMS와 다른 경우 타입·패딩 의미 차이가 결함을 숨길 수 있다.
왜 읽나JPA와 MySQL에서 UUID 기본 키를 쓰거나 H2 테스트와 운영 DB의 동작 차이를 추적하는 백엔드 개발자에게 재현 가능한 디버깅 사례가 된다.
마켓컬리
마켓컬리 Hello World 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. DB / 데이터·spotify-engSpotify Engineering·

    데이터 레이크 온라인 포인트 쿼리 인덱싱 — RAP

    Spotify 엔지니어링이 페타바이트 규모 데이터 레이크(GCS)에 저장된 Parquet 파일에서 한 자릿수 밀리초 레이턴시로 포인트 쿼리를 수행하는 RAP(Random Access Parquet) 방식을 공개했다. Trino·BigQuery 같은 분산 SQL 엔진은 단일 행 조회도 스케줄링·플래닝 비용으로 수 초가 걸리지만, RAP는 외부 인덱스로 키→(파일, 행 번호)를 직접 매핑해 의존적 read chain을 병렬화함으로써 이 문제를 해결했다. 기존 KV 스토어(Bigtable)와 달리 GB당 비용이 클라우드 스토리지 read 수준에 불과해 장기 이력 데이터나 롱테일 피처도 인터랙티브 조회 대상이 된다.

    #data-engineering#indexing#data-lake+2
  2. DB / 데이터·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    데이터 통합— MongoDB 원칙으로 document를 통합하고 동기화를 재설계하다 (3/3)

    여기어때 전시개발팀은 ES 단일 대문서 → MongoDB v1 RDB 1:1 파편화 → v2 도메인 단위 통합이라는 세 차례 구조 전환을 거쳐 전체 동기화 시간을 약 1시간에서 10분으로 단축했다. 이 글은 3부작의 마지막으로, v2 통합 문서 구조에 맞춰 "$set/$unset 필드 단위 부분 갱신 + 이벤트 발행" 기반 동기화를 어떻게 재설계했는지 구체적인 MongoDB 패턴과 함께 설명한다.

    #denormalization#data-sync#mongodb+2
  3. DB / 데이터·pinterest-engPinterest Engineering·

    Pinterest 차세대 DB 수집 프레임워크의 자동화된 스키마 진화

    Pinterest의 CDC 기반 DB 수집 파이프라인은 MySQL에서 Kafka, Flink, Spark, Iceberg를 거치는 다층 구조다. 스키마 변경이 생기면 모든 계층을 동시에 업데이트해야 해 드리프트, 배포 실패, 데이터 불일치가 반복됐다. Pinterest 엔지니어링 팀은 이를 해결하기 위해 가산적 변경만 자동화하는 스키마 진화 프레임워크를 구축하고, PR 기반 롤아웃과 SLA 기반 일관성 모델을 도입했다.

    #data-pipeline#apache-flink#cdc+2