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

데이터가 있었는데요, 아니 없어요

Master DB에 INSERT한 회원 데이터가 Slave에선 조회되는데 Master의 다른 세션에선 안 보이던 간헐적 이슈를, MVCC 스냅샷과 autocommit·open-in-view·@Transactional의 상호작용까지 파고들어 해결한 글이다.

핵심 포인트
  • Aurora MySQL(REPEATABLE READ)에서 autocommit=false인데 COMMIT/ROLLBACK/BEGIN이 안 일어나면 InnoDB의 Consistent Nonlocking Read가 최초 시점 스냅샷을 유지해 이후 커밋된 데이터를 못 본다.
  • 사용자 정의 쿼리 메서드(findByMemberNo 등)엔 @Transactional이 안 붙어 COMMIT이 없고, open-in-view=true라 Connection이 API 끝까지 유지되며, 하위 readOnly 트랜잭션의 COMMIT 탓에 ROLLBACK도 안 돼 스냅샷이 갱신되지 않았다.
  • Slave 세션은 MariaDB Connector/J aurora 모드의 readOnly 라우팅으로 매 요청 COMMIT돼 스냅샷이 갱신되니 조회되고, Master 세션만 옛 스냅샷을 물고 있어 안 보이는 현상이 생겼다.
  • 해결은 outerMethod에 @Transactional(readOnly=true)를 붙여 메서드 종료 시 COMMIT으로 스냅샷을 갱신(전체 쿼리를 Slave에서 실행)하는 것이다.
상세 정리
  • 증상: 간헐적으로 회원 데이터가 조회되지 않다가 재요청하면 조회됐다. 격리수준 REPEATABLE READ, HikariCP auto-commit=false, open-in-view=true 설정이었다.
  • Replica Lag 배제: 조회 커넥션을 디버깅하니 Slave가 아니라 Master에서 실행 중이라 복제 지연이 원인이 아니었다.
  • 진짜 원인: autocommit=false에선 종료 시점에 수동 COMMIT이 필요한데 이게 없으면 스냅샷이 갱신되지 않는다. Consistent Nonlocking Read는 트랜잭션 첫 읽기 시점 스냅샷을 유지하고, COMMIT/ROLLBACK/BEGIN이 스냅샷을 새로 뜬다.
  • 시나리오: (A세션) 기존 회원 조회로 스냅샷#1 생성 → (B세션) 신규 가입·조회 성공 → (A세션) 스냅샷#1엔 신규 데이터가 없어 조회 실패 → ROLLBACK 후 다음 요청에서 스냅샷이 갱신돼 조회 성공.
  • 해결책 비교: (1) READ COMMITTED로 낮추면 Phantom Read 사이드이펙트, (2) LOCK IN SHARE MODE 잠금 읽기는 락 경합·데드락 위험이 있어 둘 다 배제했다.
  • 채택: @Transactional(readOnly=true)를 추가해 메서드 종료 시 COMMIT으로 스냅샷을 갱신했다.
  • 함정1: Spring Data JPA 기본 메서드(findById 등)엔 @Transactional이 있지만, 인터페이스에 직접 정의한 쿼리 메서드는 없어 COMMIT이 일어나지 않는다.
  • 함정2: HikariCP는 Connection close 시 isCommitStateDirty이고 autocommit=false면 ROLLBACK을 실행한다. open-in-view=true면 Connection이 API 끝까지 유지되는데, 하위 readOnly 트랜잭션이 이미 COMMIT을 해버려 close 시 ROLLBACK이 안 나 스냅샷이 갱신되지 않았다.
  • 라우팅: aurora 모드에서 @Transactional(readOnly=true)는 Slave로, 그 외/무설정은 Master로 간다(단 Connector/J 3.0.3+는 aurora 모드 미지원).
  • 부수 성과: autocommit=false로 트랜잭션 전후 set autocommit 0/1 쿼리를 없애 API 응답시간을 1.5ms 줄여 약 40% 개선했다.
왜 읽나JPA/HikariCP/Aurora 조합에서 MVCC 스냅샷·autocommit·open-in-view가 얽혀 "있던 데이터가 안 보이는" 현상을 겪는 백엔드·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