테마
버거원 본사의 「실시간 메뉴 판매판」은 전 매장의 오늘 상품별 판매 수량을 보여 준다. 2년 전 ORD_ITEM에 행 트리거를 달아, 주문상품이 들어올 때마다 합계 테이블 ITEM_DAY_QTY의 그 상품 행에 수량을 더하게 했다.
이 문제에서 POS는 첫 상품을 찍을 때부터 ORD와 ORD_ITEM에 넣기 시작해 결제가 끝나면 COMMIT한다(보통 20 ~ 40초). 점심 피크(11:30 ~ 13:30)에 결제 저장이 몇 초씩 멈춘다는 매장 문의가 늘었고, 월말에 POS 업체가 하루치를 다시 보내 오면 그 적재도 느리다.
개발팀은 두 안을 냈다. (가) 트리거 본문을 자율 트랜잭션으로 바꿔 합계를 바로 COMMIT한다. (나) 복합 트리거(COMPOUND TRIGGER)로 바꿔 문장이 끝날 때 상품별로 모아 한 번에 더한다.
ORD_ITEM 주문상품: 2,472,717행 · 55,265블록 · 하루 약 3,400행 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · ORD_ITEM_X01 (ORD_DT, ORD_HMS)ITEM 상품: 92행 · 인기 상품 상위 5개가 판매 수량의 절반이다.ITEM_DAY_QTY 합계(문제 설정): ORD_DT · ITEM_ID · QTY · PK (ORD_DT, ITEM_ID) · 하루 92행 이하-- 2년 전 · 판매판 합계 트리거
CREATE OR REPLACE TRIGGER TRG_ORD_ITEM_QTY
AFTER INSERT ON ORD_ITEM
FOR EACH ROW
BEGIN
MERGE INTO ITEM_DAY_QTY q
USING (SELECT :NEW.ORD_DT AS ORD_DT, :NEW.ITEM_ID AS ITEM_ID FROM DUAL) s
ON (q.ORD_DT = s.ORD_DT AND q.ITEM_ID = s.ITEM_ID)
WHEN MATCHED THEN UPDATE SET q.QTY = q.QTY + :NEW.ORD_ITEM_QTY
WHEN NOT MATCHED THEN INSERT (ORD_DT, ITEM_ID, QTY)
VALUES (s.ORD_DT, s.ITEM_ID, :NEW.ORD_ITEM_QTY);
END;
-- 판매판 (1분마다)
SELECT ITEM_ID, QTY
FROM ITEM_DAY_QTY
WHERE ORD_DT = TO_CHAR(SYSDATE, 'YYYYMMDD');
[ASH — 점심 피크 · POS 세션의 대기]
EVENT 비율 대상
enq: TX - row lock contention 61% ITEM_DAY_QTY 의 상위 5개 상품 행 (I001 빅버거 세트 · I014 콜라 …)
CPU · log file sync 등 39%
POS 결제 저장 시간 평소 0.05 초 · 피크 p95 2.4 초 · 최대 31 초
[V$SESSION — 12:14:05 표본]
SID PROGRAM EVENT SECS BLOCKING_SESSION 하고 있는 문장
412 POS_0071_1 enq: TX - row lock contention 3 388 MERGE INTO ITEM_DAY_QTY …
388 POS_0133_2 SQL*Net message from client 24 (손님이 결제 중 · 트랜잭션을 연 채)
[월말 재전송 — 하루치 3,418행 다시 넣기 · TKPROF]
INSERT INTO ORD_ITEM … SELECT … FROM STG_ORD_ITEM Execute 1 rows 3,418
MERGE INTO ITEM_DAY_QTY … (트리거 안) Execute 3,418
경과 1.9 초 (트리거를 끈 시험 DB 0.2 초)
[판매판 — 지금] ITEM_DAY_QTY 에서 읽기 · Buffers 3