키 생성 병목을 추적해 구조를 바꾼 기록: Part 1 - INSERT가 느린 줄 알았다
Part 1. INSERT가 느린 줄 알았다 – 2분짜리 배치의 착각 (기술 보강판)
성능 이슈를 처음 마주하면, 우리는 거의 본능적으로 같은 곳을 본다. INSERT. 그리고 대부분의 경우, 그 판단은 절반은 맞고 절반은 틀리다.
1. 문제 상황: “실패하지 않지만, 너무 느린 배치”
문제가 된 배치는 구조적으로 단순했다.
- 입력: 계약 정보
- 처리: 계약별 기본 단가 계산
- 출력: 단가 테이블에 다건 INSERT
실행 로그는 대략 이런 흐름이었다.
1
2
3
4
[INFO] select price rate candidates finished (≈100ms)
[INFO] start inserting price rates
... (약 1~2분)
[INFO] insert price rates finished
표면적으로 보면 명확하다.
- SELECT는 빠르다
- INSERT 구간에서 대부분의 시간이 소요된다
이 지점에서 팀 내 공감대는 자연스럽게 형성됐다.
“INSERT 쪽이 병목이네.”
2. INSERT 병목이라는 가설이 그럴듯했던 이유
이 판단은 단순한 추측이 아니었다. 기술적으로도 꽤 합리적이었다.
- INSERT는 쓰기 작업
- 인덱스가 많을수록 비용 증가
- 다건 INSERT는 네트워크 왕복, SQL 파싱 비용 큼
MyBatis를 쓰고 있다면
- batch 설정이 제대로 안 됐을 가능성도 큼
그래서 첫 번째 접근은 전형적인 INSERT 튜닝이었다.
- ExecutorType.BATCH 적용
- multi-row INSERT로 변경
- 로그 출력 최소화
1
2
3
4
5
INSERT INTO price_rate (...)
VALUES
(...),
(...),
(...);
이건 “틀린 선택”은 아니었다.
3. 그런데 성능 개선이 기대만큼 나오지 않았다
튜닝 후 결과는 이랬다.
- 실행 시간: 2분 → 약 1분 30초
분명 빨라지긴 했다. 하지만 체감할 정도는 아니었다.
이 지점에서 첫 번째 의문이 생겼다.
“INSERT가 진짜 병목이라면, 이 정도 변경으로 더 크게 줄어야 하지 않나?”
특히 이상했던 건 다음 두 가지였다.
- 데이터 건수가 늘어나면 실행 시간이 선형적으로 증가
INSERT SQL 자체는 복잡하지 않음
- 트리거 없음
- FK 없음
- 서브쿼리 없음
INSERT가 느릴 이유치고는 뭔가 부족했다.
4. 로그를 다시 보기 시작했다
성능 문제를 다시 볼 때, 가장 먼저 해야 할 일은 “믿고 있던 가정”을 의심하는 것이다.
그래서 로그를 다시 쪼개서 봤다.
1
2
3
4
5
6
7
[INFO] begin transaction
[INFO] select candidates
[INFO] select finished
[INFO] insert start
... (지연)
[INFO] insert finished
[INFO] commit
여기서 중요한 사실 하나가 있다.
“INSERT가 느리게 보인다는 것”은 반드시 INSERT SQL이 느리다는 뜻은 아니다.
DB 입장에서 INSERT는:
- 실행 중일 수도 있고
실행을 기다리고 있을 수도 있다
- 락
- 트랜잭션
- 리소스 경합
하지만 애플리케이션 로그에는 그 차이가 드러나지 않는다.
5. 트랜잭션 경계를 의식하기 시작하다
이제 관점을 바꿨다.
- “INSERT가 느린가?” ❌
- “INSERT 시점에 이미 뭔가를 기다리고 있나?” ⭕
이 질문을 던지자, 자연스럽게 이 범위를 다시 보게 됐다.
- INSERT 직전까지의 모든 작업
- 같은 트랜잭션 안에서 수행되는 로직
그리고 다시 SELECT 쿼리를 들여다봤다.
6. SELECT 안에서 발견한, 너무 익숙했던 한 줄
그 쿼리는 대략 이런 형태였다.
1
2
3
4
5
SELECT
column_a,
column_b,
get_next_sequence_value(...)
FROM ...
이 함수는 단가 번호를 생성하는 역할이었다.
오래전부터 있던 코드였고, “SELECT에서 번호 하나 가져오는 정도”라고 생각해왔다.
하지만 이제는 질문이 달라졌다.
“이 함수는 정말 읽기 전용일까?” “이 함수가 DB에서 어떤 작업을 하는지, 정확히 알고 있는가?”
7. 아직은 가설일 뿐이었다
이 시점에서 사실로 확인된 건 많지 않았다.
- INSERT는 실제로 오래 걸린다
- bulk insert만으로는 해결되지 않는다
- SELECT 안에 시퀀스 관련 함수가 있다
아직은 추측의 단계였다.
하지만 중요한 변화가 하나 있었다.
문제의 범위가 “INSERT SQL” → “트랜잭션 전체”로 확장된 것이다.
8. 다음 단계로 넘어가야 할 이유
성능 문제를 풀 때 가장 위험한 순간은 “그럴듯한 원인 하나에 너무 빨리 안주하는 것”이다.
INSERT는 여전히 의심 대상이었지만, 이제는 더 이상 유일한 범인이 아니었다.
“혹시 이 배치는, INSERT가 시작되기 전부터 이미 느려질 준비를 하고 있었던 건 아닐까?”
이 질문에 답하려면, 이제 그 시퀀스 함수의 내부를 열어볼 차례였다.
다음 편 예고
Part 2. SELECT 안에서 UPDATE가 일어나고 있었다
- 시퀀스 함수의 실제 구현
- 왜 SELECT인데 락이 걸렸는지
- INSERT가 느린 ‘척’ 했던 진짜 이유
댓글
아직 댓글이 없습니다