Skip to content
튜닝 연습DML과 배치 튜닝
DML-060

휴면 예정 안내 배치가 어떤 날은 0통, 어떤 날은 어제 보낸 고객에게 또 보낸다

중급PLSQL이커머스485 / 700

이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.

상황 ​

이커머스 개발팀은 매일 02:00에 배치 프레임워크로 「휴면 예정 안내」 잡을 돌린다. 11개월 동안 주문이 없는 고객을 임시 테이블에 담고(①), 메일 발송 대기열에 넣고(②), 고객에 안내일을 표시한다(③). 9월에는 대기열이 매일 0건이었다. 개발자가 임시 테이블을 ON COMMIT PRESERVE ROWS로 바꾼 뒤로는 날마다 건수가 들쭉날쭉하다. 10월 24일 고객센터에 "휴면 안내 메일을 이틀 사이에 두 번 받았다"는 문의가 몰렸다. 개발자: "임시 테이블이 세션마다 따로라던데, 우리 잡은 한 프로그램이 도는 거니까 세션도 하나 아닌가요?"

테이블과 인덱스 ​

  • customers: 5,230행 · 51블록 · PK customer_id · UQ email. 이 문제에서는 휴면 예정 안내일 dormant_notice_dt DATE 칸이 더 있다(NULL이면 아직 안내 전).
  • orders: 34,908행 · 193블록 · order_date DATE(시분초 있음) · PK만 있다.
  • gt_dormant_target(전역 임시 테이블, 이 문제용): 원본 SQL 맨 위의 DDL이다. 9월까지는 ON COMMIT 절이 없었다.
  • notice_queue(메일 발송 대기열, 이 문제용): 발송 서버가 5분마다 읽어 보낸다. 보낸 메일은 되돌릴 수 없다.
  • 배치 프레임워크: DB 연결 풀 5개(세션은 WAS를 재기동할 때까지 며칠씩 살아 있다). 잡의 단계마다 풀에서 연결을 하나 빌려 SQL을 실행하고 커밋한 뒤 돌려준다. 어느 연결을 빌릴지는 정해져 있지 않다.

원본 SQL ​

sql
-- 임시 테이블 (지난달 수정: ON COMMIT 절 없음 → PRESERVE ROWS)
CREATE GLOBAL TEMPORARY TABLE gt_dormant_target (
  customer_id   NUMBER,
  email         VARCHAR2(200),
  last_order_dt DATE
) ON COMMIT PRESERVE ROWS;

-- 잡 「휴면 예정 안내」 · 단계마다 풀에서 연결을 빌려 실행하고 커밋한다 (:base_dt = 실행일 0시)
-- step1
INSERT INTO gt_dormant_target (customer_id, email, last_order_dt)
SELECT c.customer_id, c.email, MAX(o.order_date)
  FROM customers c JOIN orders o ON o.customer_id = c.customer_id
 WHERE c.dormant_notice_dt IS NULL
 GROUP BY c.customer_id, c.email
HAVING MAX(o.order_date) < ADD_MONTHS(:base_dt, -11);

-- step2
INSERT INTO notice_queue (customer_id, email, notice_tp, req_dt)
SELECT customer_id, email, 'DORMANT', SYSDATE
  FROM gt_dormant_target;

-- step3
UPDATE customers
   SET dormant_notice_dt = :base_dt
 WHERE customer_id IN (SELECT customer_id FROM gt_dormant_target);

실행계획 학습용 예시 ​

xplan
[잡 실행 기록 — 단계별 처리 건수와 빌려 쓴 세션]
  실행일    step1 (SID · 건수)   step2 (SID · 건수)   step3 (SID · 건수)
  10/21     412 ·  298           412 ·  298           415 ·    0
  10/22     415 ·  305           388 ·    0           412 ·  298
  10/23     412 ·   13           412 ·  311           412 ·  311

[10/24 아침에 센 것]
  이틀 사이 같은 메일을 두 번 받은 고객        298명 (10/21 · 10/23)
  세션 415 의 gt_dormant_target 에 남은 행     305행 (10/22 step1 이 넣은 것)

[9월 — ON COMMIT 절이 없던 때 (매일 같은 모양)]
  step1 290 ~ 300 · step2 0 · step3 0

[딕셔너리]
  SELECT temporary, duration FROM user_tables WHERE table_name = 'GT_DORMANT_TARGET';
  Y   SYS$SESSION

문제 ​

  1. 사흘 동안 단계마다 건수가 서로 어긋난 이유를 세션 번호로 설명하시오. ON COMMIT 절이 없던 9월에 step2와 step3이 늘 0건이던 이유도 쓰시오.
  2. 단계가 어느 연결에서 돌든 결과가 같고, 다시 돌려도 같은 고객에게 두 번 보내지 않도록 배치를 고치시오.
  3. 고친 판으로 바꾸기 전에 할 일(남아 있는 임시 테이블 행)과, 이미 두 번 보낸 298명을 어떻게 다룰지 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자