개요
지난 글에 이어 2,000개의 세션에서 1,000개의 티켓을 예약하는 시나리오를 가정하고 Lock-Free Reservation, Pessimistic Locking, FOR UPDATE SKIP LOCKED 방식의 성능을 측정해보겠습니다. 참고로 Oracle 23c Free는 2 코어만 지원하므로 향후 추가 테스트가 필요할 것으로 보입니다.
Lock-Free Reservation
Lock-Free Reservation 방식을 테스트를 위해 아래와 같이 테이블을 생성하겠습니다.
-- 1 ALTER TABLE t1 MODIFY (c2 NOT RESERVABLE); DROP TABLE t1 PURGE; DROP TABLE t2 PURGE; CREATE TABLE t1 ( c1 NUMBER , c2 NUMBER RESERVABLE , CONSTRAINT t1_pk PRIMARY KEY (c1) , CONSTRAINT t1_c1 CHECK (c2 >= 0) ); CREATE TABLE t2 (c1 TIMESTAMP, c2 NUMBER); INSERT INTO t1 VALUES (1, 1000); COMMIT;
Lock-Free Reservation 방식은 아래와 같은 방식으로 예약을 처리합니다. 예약에 필요한 작업에 0.1초가 소요된다고 가정합니다.
-- 2 CREATE OR REPLACE PROCEDURE p1 (i_v1 IN NUMBER) IS e_check_constraint_violated EXCEPTION; PRAGMA EXCEPTION_INIT (check_constraint_violated, -2290); v1 NUMBER; BEGIN UPDATE t1 SET c2 = c2 - 1 WHERE c1 = 1; DBMS_SESSION.SLEEP (0.1); -- do something INSERT INTO t2 VALUES (SYSTIMESTAMP, i_v1); COMMIT; EXCEPTION WHEN e_check_constraint_violated THEN ROLLBACK; END; /
DBMS_JOB 패키지로 동시에 2000개의 예약 작업을 수행하겠습니다.
-- 3 ALTER SYSTEM SET job_queue_processes = 2000; DECLARE v_job NUMBER; v_next_date DATE := SYSDATE + INTERVAL '20' SECOND; BEGIN FOR i IN 1 .. 2000 LOOP DBMS_JOB.SUBMIT (v_job, 'P1 (' || i || ');', v_next_date); COMMIT; END LOOP; END; / ALTER SYSTEM RESET job_queue_processes;
아래 쿼리로 결과를 확인할 수 있습니다. 수행 시간은 10초, 초당 평균 예약 건수는 100건입니다.
-- 4-1 SELECT COUNT (*) AS cnt , MIN (c2) AS min_c2 , MAX (c2) AS max_c2 , MAX (c1) - MIN (c1) AS elapsed FROM t2; CNT MIN_C2 MAX_C2 ELAPSED ---- ------ ------ -------------------------- 1000 1 1006 +000000000 00:00:10.341758 1개의 행이 선택되었습니다. -- 4-2 SELECT TO_CHAR (c1, 'MI:SS') AS c1 , COUNT (*) AS cnt FROM t2 GROUP BY TO_CHAR (c1, 'MI:SS') ORDER BY 1; C1 CNT ----- --- 00:01 69 00:02 103 00:03 92 00:04 105 00:05 110 00:06 97 00:07 90 00:08 87 00:09 80 00:10 103 00:11 64 11 행이 선택되었습니다.
Pessimistic Locking
Pessimistic Locking 방식을 테스트하기 위해 아래와 같이 테이블을 생성하겠습니다.
-- 5 DROP TABLE t3 PURGE; DROP TABLE t4 PURGE; CREATE TABLE t3 ( c1 NUMBER , c2 NUMBER , CONSTRAINT t3_pk PRIMARY KEY (c1) , CONSTRAINT t3_c1 CHECK (c2 >= 0) ); CREATE TABLE t4 (c1 TIMESTAMP, c2 NUMBER); INSERT INTO t3 VALUES (1, 1000); COMMIT;
Pessimistic Locking 방식은 아래와 같은 방식으로 예약을 처리합니다.
-- 6 CREATE OR REPLACE PROCEDURE p2 (i_v1 IN NUMBER) IS v1 NUMBER; BEGIN SELECT c1 INTO v1 FROM t3 FOR UPDATE; IF v1 >= 0 THEN UPDATE t3 SET c2 = c2 - 1 WHERE c1 = 1; DBMS_SESSION.SLEEP (0.1); -- do something INSERT INTO t4 VALUES (SYSTIMESTAMP, i_v1); COMMIT; ELSE ROLLBACK; END IF; END; /
DBMS_JOB 패키지로 동시에 2000개의 예약 작업을 수행하겠습니다.
-- 7 ALTER SYSTEM SET job_queue_processes = 2000; DECLARE v_job NUMBER; v_next_date DATE := SYSDATE + INTERVAL '20' SECOND; BEGIN FOR i IN 1 .. 2000 LOOP DBMS_JOB.SUBMIT (v_job, 'P2 (' || i || ');', v_next_date); COMMIT; END LOOP; END; / ALTER SYSTEM RESET job_queue_processes;
아래 쿼리로 결과를 확인할 수 있습니다. 수행 시간은 102초, 초당 평균 예약 건수는 10건입니다.
-- 8-1 SELECT COUNT (*) AS cnt , MIN (c2) AS min_c2 , MAX (c2) AS max_c2 , MAX (c1) - MIN (c1) AS elapsed FROM t4; CNT MIN_C2 MAX_C2 ELAPSED ---- ------ ------ -------------------------- 1000 1 1015 +000000000 00:01:42.083347 1개의 행이 선택되었습니다. -- 8-2 SELECT TO_CHAR (c1, 'MI:SS') AS c1 , COUNT (*) AS cnt FROM t4 GROUP BY TO_CHAR (c1, 'MI:SS') ORDER BY 1; C1 CNT ----- --- 00:01 7 00:02 10 00:03 10 00:04 9 00:05 10 00:06 10 00:07 10 00:08 10 00:09 9 00:10 10 ... 103 행이 선택되었습니다.
FOR UPDATE SKIP LOCKED
FOR UPDATE SKIP LOCKED 방식을 테스트하기 위해 아래와 같이 테이블을 생성하겠습니다.
-- 9 DROP TABLE t5 PURGE; CREATE TABLE t5 ( c1 NUMBER , c2 NUMBER , c3 TIMESTAMP , c4 NUMBER , CONSTRAINT t5_pk PRIMARY KEY (c1, c2) ) ORGANIZATION INDEX; INSERT INTO t5 (c1, c2) SELECT 1, ROWNUM FROM XMLTABLE ('1 to 1000'); COMMIT;
FOR UPDATE SKIP LOCKED 방식은 아래와 같은 방식으로 예약을 처리합니다.
-- 10 CREATE OR REPLACE PROCEDURE p3 (i_v1 IN NUMBER) IS BEGIN FOR f1 IN (SELECT * FROM t5 WHERE c1 = 1 AND c3 IS NULL FOR UPDATE SKIP LOCKED) LOOP BEGIN UPDATE t5 SET c3 = SYSTIMESTAMP, c4 = i_v1 WHERE c1 = f1.c1 AND c2 = f1.c2; DBMS_SESSION.SLEEP (0.1); -- do something COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; END; EXIT; END LOOP; END; /
DBMS_JOB 패키지로 동시에 2000개의 예약 작업을 수행하겠습니다.
-- 11 ALTER SYSTEM SET job_queue_processes = 2000; DECLARE v_job NUMBER; v_next_date DATE := SYSDATE + INTERVAL '20' SECOND; BEGIN FOR i IN 1 .. 2000 LOOP DBMS_JOB.SUBMIT (v_job, 'P3 (' || i || ');', v_next_date); COMMIT; END LOOP; END; / ALTER SYSTEM RESET job_queue_processes;
아래 쿼리로 결과를 확인할 수 있습니다. 수행 시간은 10초, 초당 평균 예약 건수는 100건입니다.
-- 12-1 SELECT COUNT (*) AS cnt , MIN (c4) AS min_c4 , MAX (c4) AS max_c4 , MAX (c3) - MIN (c3) AS elapsed FROM t5; CNT MIN_C4 MAX_C4 ELAPSED ---- ------ ------ -------------------------- 1000 1 1001 +000000000 00:00:09.361067 1개의 행이 선택되었습니다. -- 12-2 SELECT TO_CHAR (c3, 'MI:SS') AS c3 , COUNT (*) AS cnt FROM t5 GROUP BY TO_CHAR (c3, 'MI:SS') ORDER BY 1; C3 CNT ----- ---- 00:01 80 00:02 100 00:03 94 00:04 97 00:05 137 00:06 123 00:07 111 00:08 88 00:09 120 00:10 50 10 행이 선택되었습니다.