목록으로

HikariCP auto-commit:false, 데이터가 조용히 사라지는 날

7

HikariCP auto-commit:false, 데이터가 조용히 사라지는 날

2026-02-26


발단

FCM(Firebase Cloud Messaging, 구글에서 제공하는 모바일 푸시 알림 서비스) 발송 실패한 토큰을 DB에서 비활성 처리하는 기능을 배포했다. 로직은 단순했다.

  1. FCM 에러 응답이 오면
  2. 해당 사용자의 push token을 비활성으로 UPDATE

코드도 깔끔하고 테스트도 통과했다. 그런데 배포 후 DB를 확인하니 아무것도 바뀌지 않았다. 에러 로그도 없다.


auto-commit이 뭔데?

JDBC 커넥션에는 auto-commit이라는 설정이 있다. → auto-commit은 DB에 쿼리를 실행한 뒤 자동으로 결과를 확정(커밋)할지 말지를 정하는 설정이다.

  • auto-commit: true (기본값) → 쿼리 하나 실행하면 바로 커밋
  • auto-commit: false → 명시적으로 commit()을 호출해야 반영
auto-commit: true
  UPDATE → 바로 커밋 ✅

auto-commit: false
  UPDATE → commit() 호출 필요
         → 안 하면? 커넥션 반환 시 롤백 💀

원인

HikariCP(자바 애플리케이션에서 가장 널리 쓰이는 DB 커넥션 풀 라이브러리로, Spring Boot의 기본 커넥션 풀이다) 설정을 보니 auto-commit: false로 되어 있었다.

hikari:
  auto-commit: false

문제의 코드:

// @Transactional 없음!
public void markTokenAsInvalid(String userId) {
    jdbcTemplate.update("UPDATE ... SET PUSH_TOKEN = 'INVALID' WHERE USER_ID = ?", userId);
}

JdbcTemplate은 커넥션 풀(미리 만들어 둔 DB 연결들을 모아두고 필요할 때 빌려 쓰는 저장소)에서 커넥션을 빌려서 SQL을 실행하고 반환한다. auto-commit: true면 실행 즉시 커밋이 되지만, false면?

1. 커넥션 획득 (auto-commit: false)
2. SQL 실행 → UPDATE 수행 (아직 미확정)
3. commit()? → 아무도 안 부름
4. 커넥션 반환 → HikariCP가 rollback

쿼리는 실행됐지만 커밋 없이 사라진 것이다. 에러 없이.


해결

@Transactional
public void markTokenAsInvalid(String userId) {
    jdbcTemplate.update("UPDATE ... SET PUSH_TOKEN = 'INVALID' WHERE USER_ID = ?", userId);
}

@Transactional이 있으면 Spring이 시작-커밋-롤백을 관리해준다.


같은 날 발견한 또 다른 케이스 (2026-02-06)

비동기 스레드에서도 같은 문제가 있었다.

CompletableFuture.runAsync(() -> {
    repository.updateProgress(requestId, sent, failed);
    // → 부모의 @Transactional이 전파되지 않음!
});

Spring의 @Transactional은 ThreadLocal 기반이다. 새 스레드에는 전파되지 않는다. → ThreadLocal은 각 스레드가 자기만의 데이터를 따로 저장하는 공간이다. 새로운 스레드가 생기면 부모 스레드의 ThreadLocal 값은 복사되지 않는다.

메인 스레드 (@Transactional 있음)
  └── runAsync → 새 스레드 (트랜잭션 없음!)
       └── UPDATE → auto-commit:false → 커밋 안 됨

해결은 호출되는 쪽(Repository) 메서드에 @Transactional을 직접 붙이는 것.


배운 것

  1. auto-commit: false면 모든 쓰기에 @Transactional 필수 — JdbcTemplate이든 JPA든.
  2. 이 버그는 에러 없이 조용히 데이터를 잃는다. 로그에 아무것도 안 남는다.
  3. 비동기 스레드는 부모의 트랜잭션을 물려받지 않는다.
  4. 로컬(auto-commit: true)에서 개발하고 운영(auto-commit: false)에 배포하면 이런 사고가 난다. 환경 설정을 맞춰놓자.