테마
이 문제는 교재 18화 졸업 과제와 같은 이커머스 데이터베이스를 씁니다. 『튜너의 1년』을 순서대로 읽고 있다면 졸업 과제를 먼저 풀고 오세요. 아래 「테이블과 인덱스」 칸에 과제의 답 일부(제약 조건, 인덱스, 통계의 실제 상태)가 나옵니다.
이커머스의 포인트 적립 API는 WAS 연결 풀(세션 40개)에서 패키지 pkg_point를 부른다. 10월 8일 14:00에 적립 규칙을 고쳐 패키지 본문을 배포하자, 세션 40개가 처음 부를 때 한 번씩 ORA-04068을 냈다. 그 40건의 적립은 다시 시도되지 않아 고객 문의가 들어왔다.
개발자는 10월 10일 스펙과 본문에 PRAGMA SERIALLY_REUSABLE을 붙였다. 15일 배포 때는 ORA-04068이 나지 않았다. 그런데 API 평균 응답이 1.8ms에서 6.9ms가 됐고, 16일 새벽 야간 포인트 대사가 에러로 멈췄다.
customers: 5,230행 · 51블록 · PK(customer_id) · grade 4종 · 등급은 매일 새벽 재산정한다point_rule(이 문제용): 4행 · PK(grade) · earn_rate 등급별 적립률point_transactions: 45,000행 · 210블록 · PK(tx_id) · tx_id는 시퀀스 seq_tx_idpkg_point.rate_of를 부른다.CREATE OR REPLACE PACKAGE pkg_point IS
PRAGMA SERIALLY_REUSABLE; -- 10/10 추가 : ORA-04068 대책
PROCEDURE earn(p_customer_id NUMBER, p_amount NUMBER);
FUNCTION rate_of(p_customer_id NUMBER) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_point IS
PRAGMA SERIALLY_REUSABLE;
TYPE t_grade IS TABLE OF customers.grade%TYPE INDEX BY PLS_INTEGER;
TYPE t_rate IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
g_grade t_grade; -- 고객 5,230명의 등급 (처음 부를 때 한 번 채운다)
g_rate t_rate; -- 등급별 적립률
g_loaded BOOLEAN := FALSE;
PROCEDURE load IS
BEGIN
FOR c IN (SELECT customer_id, grade FROM customers) LOOP
g_grade(c.customer_id) := c.grade;
END LOOP;
FOR r IN (SELECT grade, earn_rate FROM point_rule) LOOP
g_rate(r.grade) := r.earn_rate;
END LOOP;
g_loaded := TRUE;
END;
FUNCTION rate_of(p_customer_id NUMBER) RETURN NUMBER IS
BEGIN
IF NOT g_loaded THEN load; END IF;
RETURN g_rate(g_grade(p_customer_id));
END;
PROCEDURE earn(p_customer_id NUMBER, p_amount NUMBER) IS
v_pt NUMBER;
BEGIN
v_pt := ROUND(p_amount * rate_of(p_customer_id));
INSERT INTO point_transactions (tx_id, customer_id, amount, tx_type, created_at)
VALUES (seq_tx_id.NEXTVAL, p_customer_id, v_pt, 'EARN', SYSDATE);
END;
END;
/
[API earn 한 번 · 평균 (V$MYSTAT 증가분)]
10/8 이전 (상태 있음) 지금 (SERIALLY_REUSABLE)
경과 1.8 ms 6.9 ms
CPU 0.6 ms 5.4 ms
recursive calls 3 58
session logical reads 9 66
[저녁 피크 1시간 — AWR]
API 호출 27,400번
SELECT CUSTOMER_ID, GRADE FROM CUSTOMERS Executions 0 → 27,400 · Buffer Gets 0 → 1,397,400
SELECT GRADE, EARN_RATE FROM POINT_RULE Executions 0 → 27,400
DB CPU 41 초 → 176 초
[10/8 14:00 배포 직후 — WAS 로그 (세션마다 한 번, 40건)]
ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package body "ECOMMERCE.PKG_POINT" has been invalidated
[10/16 02:10 야간 포인트 대사]
SELECT o.order_id, o.customer_id, pkg_point.rate_of(o.customer_id) AS rate FROM orders o WHERE …
ORA-06534: Cannot access Serially Reusable package "ECOMMERCE.PKG_POINT"
SERIALLY_REUSABLE을 붙인 뒤 API가 느려진 이유와 야간 대사가 멈춘 이유를 설명하시오. 처음에 ORA-04068이 난 이유도 쓰시오.