분류

트랜잭션·락·DB 운영

락 대기, 격리 수준, @Transactional 함정, 스키마 변경과 VACUUM 처럼 운영 중인 DB 에서 터지는 문제를 모았어요.

21편 · 마지막 글 2026.08.12

DB 운영 글

21편 중 1–12

  1. @Transactional(readOnly = true) 를 안 붙이면 복제본으로 안 간다

    읽기 복제본을 붙였는데 주 서버 부하가 그대로예요. 라우팅 설정도 맞는데요. 조회만 하는 코드도 읽기 전용이라고 선언하지 않으면 쓰기로 취급되는 이유와 그 한 줄이 바꾸는 것을 정리했어요.

    5분#DB
  2. 배치를 트랜잭션 하나로 돌리지 마라

    백만 건 야간 배치를 트랜잭션 하나로 감쌌더니, 도는 두 시간 동안 서비스가 느려지고 실패하면 처음부터 다시 해야 했어요. 배치를 잘게 나눠 중간부터 이어 하게 만드는 법을 정리했어요.

    5분#DB
  3. 낙관적 락 vs 비관적 락 — 충돌 확률로 고르는 기준

    낙관적 잠금은 빠르고 비관적 잠금은 안전하다는 말, 둘 다 맞게 들려서 더 헷갈려요. 실제로는 충돌이 얼마나 자주 나느냐에 따라 유불리가 뒤집혀요. 상황별로 고르는 기준을 정리했어요.

    5분#DB
  4. 트랜잭션은 짧게 — 경계가 곧 비용이다

    커넥션 부족, 테이블 팽창, 락 경합이 한꺼번에 나서 하나씩 따로 조사했는데, 알고 보니 원인은 트랜잭션이 길다는 것 하나였어요. 경계를 어디에 두고 어떻게 줄이는지 정리했어요.

    5분#DB
  5. 아웃박스 패턴 — 커밋되기 전에 이벤트를 보내지 마라

    주문을 저장하고 바로 알림 이벤트를 보냈더니, 가끔 알림 서비스가 「그런 주문 없는데요」라고 해요. 취소된 주문 알림이 나가기도 하고요. 저장과 발행을 안전하게 묶는 방법을 정리했어요.

    5분#DB
  6. SKIP LOCKED 로 만드는 작업 큐

    DB 테이블로 만든 작업 큐에 워커를 다섯으로 늘렸는데, 처리량은 한 대일 때와 똑같아요. 모두 맨 앞 작업 하나에 줄을 서기 때문이에요. 잠긴 행은 건너뛰는 방식으로 푸는 법을 정리했어요.

    5분#DB
  7. REQUIRES_NEW 가 커넥션 풀을 말린다 — 요청 하나가 커넥션 둘을 쥘 때

    로그만은 남기려고 저장 메서드를 별도 트랜잭션으로 뺐어요. 그런데 트래픽이 오르자 CPU 도 DB 도 한가한데 앱이 멈춰요. 요청 하나가 커넥션 둘을 쥐면서 생기는 교착과 피하는 법을 다뤄요.

    5분#DB
  8. @Transactional 롤백이 안 될 때 — 자기 호출과 프록시

    트랜잭션 애너테이션을 붙였는데 예외가 나도 롤백이 안 돼요. 오타도 없고 임포트도 맞는데요. 트랜잭션이 아예 시작되지 않는 이유와, 같은 클래스 안에서 부를 때 생기는 일을 정리했어요.

    5분#DB
  9. 쓰기 스큐 — 각자는 옳은데 합치면 틀리다

    당직은 최소 한 명이 남아야 하는데, 두 사람이 동시에 해제를 눌렀더니 0명이 됐어요. 검사도 트랜잭션도 있었는데요. 각자는 옳은데 합치면 틀리는 문제와 이를 막는 방법을 다뤄요.

    5분#DB
  10. 갱신 손실(lost update) — 동시에 들어온 포인트 적립이 한 번만 반영될 때

    포인트 적립 요청이 두 번 다 성공했는데 잔액에는 한 번만 들어가 있어요. 에러가 없어서 정산 때가 돼서야 알게 되죠. 동시 요청이 한쪽 쓰기를 덮어쓰는 이유와 막는 방법을 정리했어요.

    5분#DB
  11. 트랜잭션 안에서 외부 API 를 부르지 마라

    결제사가 조금 느려졌을 뿐인데 결제와 상관없는 화면까지 우리 서비스가 통째로 멈췄어요. 트랜잭션 안에서 외부 API 를 부른 게 원인이었죠. 왜 그런지와 호출을 밖으로 빼는 법을 다뤄요.

    5분#DB
  12. JSON 컬럼은 스키마를 미루는 빚이다

    요구사항이 자주 바뀌어 JSON 컬럼에 속성을 몰아넣었더니, 1년 뒤엔 어떤 키가 있는지 아무도 몰라요. 조회도 느려졌고요. 빨리 가려던 선택이 빚이 되는 과정과 갚는 순서를 정리했어요.

    5분#DB