Skip to content
튜닝 연습Lock과 동시성 제어
LOCK-027

전송 작업을 넷으로 늘렸는데 점심 큐는 하나일 때처럼 쌓인다

중급PLSQL버거522 / 700

상황 ​

버거 본사는 매장 POS 주문을 가맹 정산 시스템으로 보내는 전송 큐를 둔다. POS가 주문을 넣을 때마다 큐에 한 행이 쌓이고, 본사의 전송 작업이 100건씩 집어 보낸 뒤 DONE으로 바꾼다. 점심 피크에 큐가 1만 건 넘게 쌓여 정산 시스템의 매출이 40분씩 늦어지자, 지난주 전송 작업을 하나에서 넷으로 늘렸다. 쌓이는 모양은 그대로다. 개발팀은 이렇게 말한다. "SKIP LOCKED를 넣었으니 넷이 서로 다른 건을 집을 거예요. 정산 시스템이 느린 것 같아요."

테이블과 인덱스 ​

  • ORD_SEND_Q 전송 큐(이 문제의 파생 테이블): Q_SEQ (PK, 시퀀스) · ORD_NO · SHOP_NO · SEND_ST (READY · DONE) · REG_DTT · 인덱스 ORD_SEND_Q_X01 (SEND_ST, Q_SEQ) · DONE 행은 밤에 지운다
  • 점심 피크(11:30~13:00)에는 1분에 300건 남짓 쌓인다.
  • 정산 시스템은 100건 묶음 하나를 받는 데 약 30초 걸린다(건마다 검증). 작업 하나는 1분에 약 200건을 보낸다.
  • 작업 넷(W1~W4)은 DBMS_SCHEDULER 잡으로 같은 프로시저를 돈다.

원본 SQL ​

sql
PROCEDURE SEND_WORKER IS
  TYPE t_q IS TABLE OF ORD_SEND_Q%ROWTYPE;
  v_q t_q;
BEGIN
  LOOP
    SELECT * BULK COLLECT INTO v_q
      FROM ORD_SEND_Q
     WHERE SEND_ST = 'READY'
       AND ROWNUM <= 100
       FOR UPDATE SKIP LOCKED;                  -- 남이 잡은 건은 건너뛴다

    IF v_q.COUNT = 0 THEN
      DBMS_SESSION.SLEEP(30);                   -- 큐가 비었으면 30초 쉰다
    ELSE
      PKG_STLM_IF.SEND(v_q);                    -- 정산 시스템으로 보낸다 (100건에 약 30초)
      FORALL i IN 1 .. v_q.COUNT
        UPDATE ORD_SEND_Q SET SEND_ST = 'DONE' WHERE Q_SEQ = v_q(i).Q_SEQ;
      COMMIT;
    END IF;
  END LOOP;
END;

실행계획 학습용 예시 ​

xplan
[「다음 100건」 한 번 — W2 · W1 이 100건을 쥐고 보내는 중]
---------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                |      1 |        |      0 |00:00:00.01 |     104 |
|   1 |  FOR UPDATE                    |                |      1 |        |      0 |00:00:00.01 |     104 |
|*  2 |   COUNT STOPKEY                |                |      1 |        |    100 |00:00:00.01 |       4 |
|   3 |    TABLE ACCESS BY INDEX ROWID | ORD_SEND_Q     |      1 |    100 |    100 |00:00:00.01 |       4 |
|*  4 |     INDEX RANGE SCAN           | ORD_SEND_Q_X01 |      1 |    100 |    100 |00:00:00.01 |       2 |
---------------------------------------------------------------------------------------------------------
   2 - filter(ROWNUM<=100)
   4 - access("SEND_ST"='READY')

[작업 넷 — 12:00 ~ 12:10]
작업   집은 묶음   보낸 건    0건을 받고 쉰 횟수
W1        19       1,900          0
W2         1         100         18
W3         0           0         20
W4         1         100         19
큐 (READY)  12:00 9,850건 → 12:10 10,920건   (그동안 3,170건이 들어왔다)

[V$SESSION — 12:05:00]
SID   EVENT                SECS   하고 있는 일
311   TCP Socket (KGAS)      12   PKG_STLM_IF.SEND (W1 · 100건을 쥔 채 보내는 중)
318   PL/SQL lock timer      21   DBMS_SESSION.SLEEP (W2)
325   PL/SQL lock timer       7   DBMS_SESSION.SLEEP (W3)
332   PL/SQL lock timer      28   DBMS_SESSION.SLEEP (W4)
(enq: TX - row lock contention 은 하나도 없다)

문제 ​

  1. 실행계획과 작업 넷의 기록을 근거로 SKIP LOCKED를 넣었는데도 작업 셋이 놀고 있는 이유를 설명하시오.
  2. 넷이 동시에 서로 다른 100건을 집도록 고치시오. 고친 판에서 주의할 점도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자