모든 글 · 2쪽
전체 글 124편 중 13–24번째 글이에요. 최신 글이 앞에 있어요.
2쪽 글 목록
124편 중 13–24
LIMIT 이 붙으면 계획이 통째로 바뀐다
LIMIT 10 을 붙였더니 오히려 느려졌어요. LIMIT 10 은 0.1초인데 LIMIT 100 은 12초가 걸리기도 하고요. LIMIT 값 하나에 옵티마이저가 계획을 통째로 바꾸는 이유와 대처법을 정리했어요.
EXPLAIN ANALYZE 읽는 법 — 추정 행 수와 실제 행 수 비교하기
느린 쿼리를 잡으려고 EXPLAIN 을 열어도 숫자가 무슨 뜻인지 모르겠어요. 인덱스 스캔인데도 느리면 더 막막하고요. 실행 계획에서 먼저 봐야 할 두 숫자와 읽는 순서를 정리했어요.
@Transactional(readOnly = true) 를 안 붙이면 복제본으로 안 간다
읽기 복제본을 붙였는데 주 서버 부하가 그대로예요. 라우팅 설정도 맞는데요. 조회만 하는 코드도 읽기 전용이라고 선언하지 않으면 쓰기로 취급되는 이유와 그 한 줄이 바꾸는 것을 정리했어요.
배치를 트랜잭션 하나로 돌리지 마라
백만 건 야간 배치를 트랜잭션 하나로 감쌌더니, 도는 두 시간 동안 서비스가 느려지고 실패하면 처음부터 다시 해야 했어요. 배치를 잘게 나눠 중간부터 이어 하게 만드는 법을 정리했어요.
낙관적 락 vs 비관적 락 — 충돌 확률로 고르는 기준
낙관적 잠금은 빠르고 비관적 잠금은 안전하다는 말, 둘 다 맞게 들려서 더 헷갈려요. 실제로는 충돌이 얼마나 자주 나느냐에 따라 유불리가 뒤집혀요. 상황별로 고르는 기준을 정리했어요.
트랜잭션은 짧게 — 경계가 곧 비용이다
커넥션 부족, 테이블 팽창, 락 경합이 한꺼번에 나서 하나씩 따로 조사했는데, 알고 보니 원인은 트랜잭션이 길다는 것 하나였어요. 경계를 어디에 두고 어떻게 줄이는지 정리했어요.
아웃박스 패턴 — 커밋되기 전에 이벤트를 보내지 마라
주문을 저장하고 바로 알림 이벤트를 보냈더니, 가끔 알림 서비스가 「그런 주문 없는데요」라고 해요. 취소된 주문 알림이 나가기도 하고요. 저장과 발행을 안전하게 묶는 방법을 정리했어요.
SKIP LOCKED 로 만드는 작업 큐
DB 테이블로 만든 작업 큐에 워커를 다섯으로 늘렸는데, 처리량은 한 대일 때와 똑같아요. 모두 맨 앞 작업 하나에 줄을 서기 때문이에요. 잠긴 행은 건너뛰는 방식으로 푸는 법을 정리했어요.
REQUIRES_NEW 가 커넥션 풀을 말린다 — 요청 하나가 커넥션 둘을 쥘 때
로그만은 남기려고 저장 메서드를 별도 트랜잭션으로 뺐어요. 그런데 트래픽이 오르자 CPU 도 DB 도 한가한데 앱이 멈춰요. 요청 하나가 커넥션 둘을 쥐면서 생기는 교착과 피하는 법을 다뤄요.
@Transactional 롤백이 안 될 때 — 자기 호출과 프록시
트랜잭션 애너테이션을 붙였는데 예외가 나도 롤백이 안 돼요. 오타도 없고 임포트도 맞는데요. 트랜잭션이 아예 시작되지 않는 이유와, 같은 클래스 안에서 부를 때 생기는 일을 정리했어요.
쓰기 스큐 — 각자는 옳은데 합치면 틀리다
당직은 최소 한 명이 남아야 하는데, 두 사람이 동시에 해제를 눌렀더니 0명이 됐어요. 검사도 트랜잭션도 있었는데요. 각자는 옳은데 합치면 틀리는 문제와 이를 막는 방법을 다뤄요.
갱신 손실(lost update) — 동시에 들어온 포인트 적립이 한 번만 반영될 때
포인트 적립 요청이 두 번 다 성공했는데 잔액에는 한 번만 들어가 있어요. 에러가 없어서 정산 때가 돼서야 알게 되죠. 동시 요청이 한쪽 쓰기를 덮어쓰는 이유와 막는 방법을 정리했어요.
조건에 맞는 글이 없어요. 브루니가 숨었나 봐요.