트랙 CC3180분

대기열, 자원, 시뮬레이션

실습 28의 whatif.sh는 3~5월 업무의 첫 리뷰 대기를 4업무시간으로 자르면 처리 시간 중앙값이 36.8h에서 33.1h가 된다고 어림했고, 리뷰 당번을 시작한 6~9월의 실제 값은 27.7h였다. 어림은 “대기가 줄면 뒤의 사건이 그만큼 앞당겨진다”고만 가정할 뿐, 누가 리뷰하는지, 리뷰어가 바쁘면 대기가 어떻게 늘어나는지는 다루지 않는다. 그래서 리뷰어를 한 명 늘리는 것과 리뷰 당번을 두는 것 중 어느 쪽이 대기를 더 줄이는지에 답하지 못한다. 이 모듈은 PR 첫 리뷰를 대기열로 보고 로그에서 도착과 대기를 재고, 사람과 분기를 분석한 뒤, SimPy로 만든 시뮬레이션을 두 기간의 실제 값으로 검증하고 정책을 비교한다.

진행
설명 50분, 실습 110분, 정리 20분
결과
대기열 보고(pm-lab/queue-a.md), 조직·결정 마이닝 보고(pm-lab/org.md), 시뮬레이션 정책 비교표(pm-lab/sim-a.md, pm-lab/sim-b.md)
명령
queue-mining.py, org-mining.py, review-sim.py, whatif.sh
cd ~/gh-pm-lab

이 모듈의 시간은 모두 업무 시간(평일 09~18시)이고, 기간은 2단계와 같이 작업 시작 신호의 날짜로 나눈다(3~5월은 --to 2026-05-31, 6~9월은 --from 2026-06-01).

1대기열의 말

말뜻PR 리뷰에서
도착률 λ단위 시간에 들어오는 일의 수업무 시간당 만들어지는 PR 수
처리 시간 S (처리율 μ = 1/S)창구 하나가 일 하나를 처리하는 데 드는 시간리뷰어가 PR 하나를 읽고 리뷰를 남기는 시간. 로그에는 리뷰를 시작한 시각이 없어 직접 잴 수 없다
창구 수 c동시에 처리할 수 있는 수그 PR을 맡을 수 있는 리뷰어 수
이용률 ρ = λS / c창구가 바쁜 시간의 비율리뷰어가 리뷰에 쓰는 시간의 비율
리틀의 법칙 L = λW안정된 대기열에서 평균 대기 개수 L은 도착률과 평균 체류 시간 W의 곱이다(리틀 1961)실습 15에서 진행 중 업무 수와 처리량, 리드 타임의 관계로 썼다

도착이 포아송 과정이고 처리 시간이 지수 분포이며 창구가 c개인 대기열을 M/M/c라고 적는다(켄들 표기). M/M/c에서 기다리는 시간은 얼랑 C 공식(Erlang, 1917)으로 계산한다. 이용률마다 대기 시간 Wq가 처리 시간 S의 몇 배인지 계산해 본다.

창구가 하나일 때 이용률이 0.5면 처리 시간만큼 기다리고, 0.9면 9배, 0.95면 19배를 기다린다. 대기는 1/(1 − ρ)에 비례하므로, 이용률이 같은 폭으로 올라도 이용률이 높을 때 대기가 훨씬 많이 는다. 같은 이용률에서 창구 둘이 줄 하나를 함께 받으면(풀링) 대기는 3분의 1에서 절반 사이로 준다. 리뷰어를 늘리는 정책이 효과가 있으려면 지금의 대기가 이 곡선 위에 있어야 한다. 곧 리뷰어가 바빠서 생긴 대기여야 한다.

2로그에서 대기열 재기

대기열 마이닝(queue mining)은 이벤트 로그에서 대기열의 도착, 대기, 처리를 재고 대기열 이론의 예측을 쓰는 방법이다. 센데로비치(A. Senderovich) 등은 2015년 Information Systems 논문에서 통신사와 금융 기관의 로그로, 사례가 들어올 때의 대기열 길이를 쓰는 예측이 대기열을 무시한 예측보다 대기 시간을 잘 맞힌다는 것을 보였다. queue-mining.py는 PR 첫 리뷰를 대기열로 보고 같은 양을 센다.

  • 도착: 작업일 하루에 PR이 평균 1.78개 들어왔다. 하루 PR 수의 분산이 평균의 1.22배, 도착 간격의 변동계수가 1.24로, 포아송 도착(둘 다 1)에서 크게 벗어나지 않는다. 도착률은 업무 시간당 0.198개다.
  • 리뷰어 1: 첫 리뷰의 52%를 맡았고 평균 12.1h를 기다리게 했다. 리틀의 법칙으로 L = 0.103 × 12.1 = 1.25, 곧 평균적으로 PR 1.25개가 이 사람의 첫 리뷰를 기다리고 있었다. 나머지 다섯 명은 0.05~0.08개다.
  • 묶음 리뷰: 같은 리뷰어의 앞 리뷰와 30업무분 안에 있는 리뷰는 170건 중 9건(5%)이다. 하루 한두 번 몰아서 리뷰하는 습관(묶음 처리, batching)이 있다면 이 비율이 높게 나온다.

3리뷰어가 바빠서 생긴 대기인가

리뷰어 1의 대기를 M/M/1 대기열로 설명해 본다. 관측한 W = 12.1h와 λ = 0.103에서 처리 시간을 거꾸로 구하면 S = W / (1 + λW) = 5.4h, 이용률은 0.56이다. 이 가설이 맞다면 앞에 PR이 n건 기다릴 때 들어온 PR은 평균 (n + 1) × 5.4h를 기다려야 한다. 위 출력의 “대기열 길이와 대기” 표가 이 예측과 실제를 나란히 놓는다.

앞에 기다리던 PR 수(0, 1, 2, 3건 이상)별 첫 리뷰 대기. M/M/1 가설은 5.4h, 10.8h, 16.2h, 21.5h로 가파르게 오르지만, 실제 평균은 11.6h, 12.2h, 11.9h, 12.9h로 거의 평평하다.
리뷰어 1(3~5월)의 첫 리뷰 대기. 실제 대기는 앞에 기다리던 PR 수와 거의 무관하다. 최소제곱 기울기는 +0.3h로, 가설이 예상하는 +5.4h와 다르다.

앞에 아무 PR도 없을 때 들어온 PR 14건도 평균 11.6h를 기다렸다. 줄이 없는데 기다렸다는 것은 대기가 리뷰를 시작하기까지의 지연에서 왔고 리뷰어의 처리 능력과는 거의 관계가 없다는 뜻이다. 나카툼바(J. Nakatumba)와 판 데르 알스트는 작업 부하가 처리 시간에 주는 영향을 로그와 회귀로 재는 방법을 제안했다(“Analyzing Resource Behavior Using Process Mining”, BPM 2009 워크숍). 같은 질문을 리뷰 대기에 던지면 Orbit의 답은 “부하와 거의 무관하다”다. 6~9월도 확인한다.

6~9월에는 가장 많이 맡은 사람의 몫이 23%로 줄었고 리뷰어별 대기 중앙값이 모두 1.6~1.8h다. 3~5월에 리뷰어 1을 뺀 다섯 명(2.0~3.4h)보다도 짧다. 이 차이는 아래에서 모델을 다른 기간으로 검증할 때 다시 쓴다.

4조직 마이닝과 결정 마이닝

대기의 원인이 배정 방식에 있다면, 누가 누구의 일을 받는지와 어떤 PR이 다른 갈래로 가는지를 먼저 본다.

판 데르 알스트, 레이예르스(H. A. Reijers), 송민석은 2005년 Computer Supported Cooperative Work 논문 “Discovering Social Networks from Event Logs”에서 로그의 수행자 정보로 사람 사이의 네트워크를 만드는 방법을 정리했다. 넘김(handover of work)은 사례 안에서 일이 한 사람에게서 다음 사람으로 넘어간 횟수, 함께 일함(working together)은 두 사람이 같은 사례에 나온 정도다. 활동이 비슷한 사람끼리 묶으면 역할이 나온다. org-mining.py는 PM4Py의 세 함수(discover_handover_of_work_network, discover_working_together_network, discover_organizational_roles)를 실행하고, PR 작성자와 첫 리뷰어의 짝을 센다. 이름은 역할 파일의 역할에 번호를 붙인 가명으로 바꿔 적는다.

  • 넘김: 이어진 사건의 51%는 같은 사람이 이어서 했다. 다른 사람에게 넘어간 것 가운데 가장 많은 것은 PM 1에서 개발자로 가는 넘김이다. 업무 생성(PM) 다음의 작업 시작(개발), 막힘 해소(PM) 다음의 첫 커밋(개발)이 여기에 들어 있다.
  • 역할 발견: 활동으로 묶은 역할 네 개가 나왔다. 역할 3(리뷰 승인, 변경 요청)에서 개발 1의 사건이 120건으로, 다음 사람(49건)의 두 배를 넘는다. 실습 25의 리뷰 집중도를 다른 방법으로 다시 찾은 것이다. 역할 1에 PM 1이 들어 있는 것은 product 업무의 PR을 PM이 직접 만들기 때문이다.
  • 작성자 → 첫 리뷰어: 개발 1은 자기 PR을 제외한 모든 작성자에게서 가장 많은 첫 리뷰를 받았다. 리뷰어를 작성자가 고르면 특정 사람에게 몰린다는 것을 표로 보여 준다.
사람 네트워크는 평가에 쓰지 않는다

넘김과 함께 일함 값은 사람마다 맡은 업무의 종류와 양을 그대로 반영한다. “개발 1이 리뷰를 많이 했다”는 리뷰어를 고르는 방식의 결과이고, 고칠 대상도 그 방식이다. 그 사람을 평가하는 자료로 쓰지 않는다. 보고서에는 가명과 역할만 쓰고, --show-names는 분석자가 원인을 확인할 때만 쓴다.

결정 마이닝(decision mining)은 프로세스 모델의 분기점에서 사례가 어느 갈래로 가는지를 사례의 속성으로 설명한다. 로지나트(A. Rozinat)와 판 데르 알스트는 2006년 BPM 학회의 “Decision Mining in ProM”에서 분기점마다 결정 나무를 학습해 갈래를 가르는 조건을 찾는 방법을 제안했다. 위 출력의 마지막 표는 “PR 생성 다음의 첫 리뷰가 변경 요청인가”라는 분기를 결정 기록, 막힘, 둘째 PR, 작업 시작 시기(6월 1일 전후), area로 설명한 깊이 2의 결정 나무다.

잎PR변경 요청 비율
결정 기록 없음, 막힘 없음17122%
결정 기록 없음, 막힘 있음2512%
결정 기록 있음, 6월 1일 전2264%
결정 기록 있음, 6월 1일 이후2928%

나무는 결정 기록으로 먼저 나누고, 결정 기록이 있는 PR을 다시 시기로 나눴다. 결정 기록이 있는 PR의 변경 요청 비율이 64%에서 28%로 떨어졌고, 결정 기록이 없는 PR은 시기로 나뉘지 않았다. C2의 선언적 제약 비교(response(결정 기록, 변경 요청) 78% → 32%)와 같은 방향이다. 결정 나무는 관계를 찾을 뿐 원인을 증명하지 않는다. 6월 1일에는 리뷰 당번과 완료 조건 갱신 규칙이 함께 시작되었으므로(실습 27), 이 차이를 어느 규칙의 효과로 볼지는 나무가 답하지 않는다.

5시뮬레이션 모델 만들기와 검증

로지나트, 만스(R. S. Mans), 송민석, 판 데르 알스트는 2009년 Information Systems 논문 “Discovering simulation models”에서 로그로 흐름, 자원, 시간, 데이터의 관점을 각각 발견해 시뮬레이션 모델 하나로 합치는 방법을 보였다. review-sim.py는 그 가운데 PR 첫 리뷰 부분만 이산 사건 시뮬레이션(discrete event simulation)으로 만든다. SimPy는 프로세스(PR 하나)와 자원(리뷰어 한 명)을 Python 함수와 객체로 적는 이산 사건 시뮬레이션 라이브러리다.

모델 요소로그에서 정한 값
도착재생할 기간의 실제 PR 생성 시각과 작성자를 그대로 흘려 보낸다(--load를 주면 포아송 도착)
배정작성자가 고름: 가장 많이 맡은 사람과 나머지의 관측 비율로 뽑는다(작성자 본인 제외). 리뷰 당번: 작업일마다 한 명씩 돌아간다
delay 모델리뷰를 시작하기까지의 지연을 그 리뷰어 묶음의 관측 대기에서 뽑고, 리뷰 작업 0.5시간만 리뷰어(용량 1인 자원)를 점유한다. 대기열 길이 점검의 결과를 따른 모델이다
queue 모델리뷰어마다 M/M/1 창구로 보고, 평균 대기가 관측과 같도록 처리 시간을 정한다(리뷰어 1은 5.4h). 대기열 길이 점검에서 기각한 가설을 비교용으로 둔다
처리 시간재생한 업무마다 실제 처리 시간에서 (실제 첫 리뷰 대기 − 시뮬레이션 첫 리뷰 대기)를 뺀다
반복정책마다 200회. 반복 r은 씨앗값 1 + r을 쓰고, 정책끼리 같은 난수를 쓴다(공통 난수)

먼저 3~5월로 추정하고 3~5월의 도착을 그대로 흘려, 모델이 관측을 재현하는지 본다.

“작성자가 고름” 줄이 지금 방식의 재현이다. 첫 리뷰 대기 중앙값은 관측 6.8h에 대해 6.4h(반복 200회의 5~95% 범위 5.3~8.4h), 85%는 14.4h에 대해 14.2h, 가장 많이 맡은 사람의 몫은 52%에 대해 50%, 처리 시간 중앙값은 36.8h에 대해 37.9h(36.4~39.2h)다. 관측값이 모두 시뮬레이션의 범위 안에 있다. 대기열 점검의 기울기도 시뮬레이션 −0.0h, 관측 +0.3h로 둘 다 0에 가깝다. 같은 검증을 queue 모델로 한다.

queue 모델은 평균 대기를 관측에 맞췄는데도 중앙값이 4.1h(2.9~5.9h)로 관측 6.8h를 범위 밖에 둔다. 지수 분포의 처리 시간이 짧은 대기를 너무 많이 만들기 때문이다. 대기열 점검의 기울기도 +4.8h로 관측 +0.3h와 다르다. 두 모델 모두 평균은 맞출 수 있지만, 분포의 모양과 부하에 대한 반응으로 검증하면 queue 모델은 Orbit의 3~5월을 설명하지 못한다. 아래에서는 delay 모델을 쓴다.

6정책 비교와 실습 28의 어림

정책별 첫 리뷰 대기 중앙값을 반복 200회의 평균과 5~95% 범위로 그린 그림. 왼쪽 3~5월 재생: 관측 6.8h, 작성자가 고름 6.4h, 리뷰어 1명 추가 5.6h, 대기 한도 1건 3.8h, 리뷰 당번 3.2h. 오른쪽 6~9월 재생: 관측 1.8h, 작성자가 고름 6.4h, 리뷰어 1명 추가 5.7h, 대기 한도 1건 3.8h, 리뷰 당번 3.3h.
delay 모델(3~5월로 추정)의 정책별 첫 리뷰 대기 중앙값. 가로선은 반복 200회의 5~95% 범위, 주황 마름모는 재생한 기간의 관측값이다.
정책 (3~5월 재생)첫 리뷰 대기 중앙값처리 시간 중앙값지금 방식과의 차이
작성자가 고름 (지금 방식)6.4h37.9h-
작성자가 고름 + 리뷰어 1명 추가5.6h37.5h−0.4h
대기 한도 1건3.8h35.1h−2.8h
리뷰 당번3.2h33.4h−4.5h
(참고) whatif.sh --review-cap 4-33.1h−3.7h (실제 36.8h 기준)
  • 리뷰어를 늘려도 대기는 거의 줄지 않는다: 새 리뷰어도 다른 리뷰어와 같은 비율로만 고르면, 가장 많이 맡은 사람의 몫이 50%에서 45%로 줄 뿐이다. 대기가 처리 능력의 부족에서 오지 않았으므로 창구를 늘려도 M/M/c 표의 곡선을 따라 내려가지 않는다.
  • 배정 방식을 바꾸는 정책이 효과가 크다: 고른 리뷰어에게 이미 1건이 기다리면 가장 덜 바쁜 사람에게 넘기는 대기 한도(WIP 한도)는 몫을 28%로, 리뷰 당번은 24%로 낮춘다. 대기가 긴 사람에게 가는 PR이 줄어 중앙값과 85%가 함께 준다.
  • whatif.sh와 비교: 리뷰 당번의 처리 시간 33.4h는 어림 33.1h와 비슷하다. 시뮬레이션이 더해 주는 것은 두 가지다. 반복으로 얻은 범위(32.6~34.3h)와, 어림으로는 적을 수 없는 정책(리뷰어 추가, 대기 한도)의 비교다.

7다른 기간으로 검증하기

모델을 만든 기간으로만 검증하면 모델이 그 기간을 외웠는지 구분하지 못한다. 3~5월로 추정한 모델에 6~9월의 실제 PR 도착을 흘려 보내고, 리뷰 당번 줄을 6~9월의 관측과 비교한다.

리뷰 당번의 첫 리뷰 대기 중앙값은 3.3h(2.9~3.9h)로 예측했지만 실제는 1.8h였고, 처리 시간도 30.8h(29.9~31.7h) 예측에 실제 27.7h로, 둘 다 범위 밖이다. 모델이 놓친 것은 두 가지다.

  1. 당번의 응답 속도: 모델은 사람마다 3~5월의 응답 지연이 그대로라고 가정했다. 그러나 queue-mining.py의 6~9월 표에서 리뷰어별 대기 중앙값은 1.6~1.8h로, 3~5월에 가장 빨랐던 사람들보다도 짧다. 당번이 된 날 리뷰를 먼저 처리하는 행동의 변화가 정책과 함께 생긴 것으로 보인다. 3~5월의 기록으로 추정한 모델에는 그 행동이 없다.
  2. 리뷰 밖의 변화: 6월 이후에는 결정 기록이 있는 업무의 변경 요청이 줄었다(결정 마이닝, C2). 이 모델은 첫 리뷰 대기만 바꾸므로 그 효과는 처리 시간 예측에 들어가지 않는다.

같은 출력의 “작성자가 고름” 줄(34.4h)은 반대 방향의 물음에 답한다. 6~9월의 업무가 3~5월의 리뷰 방식으로 처리되었다면 처리 시간 중앙값이 34.4h였을 것이라는 어림이다. 모델 안에서 리뷰 당번이 줄인 몫은 6~9월 업무로 3.6h(34.4h → 30.8h), 3~5월 업무로 4.5h다. 실습 27이 다른 방법으로 얻은 4.1h와 비슷한 크기다.

검증에 실패한 시뮬레이션을 어디에 쓰는가

6~9월의 실제가 모델의 범위 밖에 나온 것은 모델의 가정(사람의 응답 속도는 정책과 무관하다) 가운데 하나가 틀렸다는 증거다. 시뮬레이션은 가정을 숫자로 시험하는 도구이고, 이 결과로 다음 개선안의 어림에 “당번 같은 책임 배정은 응답 속도도 바꿀 수 있다”를 더한다. 반대로 어림이 실제와 맞았다면 맞은 이유를 확인한 뒤에야 다음 정책의 어림에 쓴다.

8PR이 두 배로 늘면

팀이 커져 PR이 두 배가 되었을 때를 두 모델로 비교한다. --load 2는 실제 도착 대신 도착률을 두 배로 한 포아송 도착을 만든다.

delay 모델에서는 도착이 두 배여도 지금 방식 6.3h, 리뷰 당번 3.5h로 정책 비교의 값과 비슷하다. 리뷰 작업이 0.5시간뿐이라 리뷰어의 이용률이 여전히 낮기 때문이다. queue 모델에서는 지금 방식의 85%가 75.1h까지 늘어난다. 리뷰어 1의 이용률이 0.21 × 5.4 ≈ 1.1로 1을 넘어 관측 기간 내내 줄이 길어지는, M/M/c 표에서 이용률이 1에 다가갈 때의 모습이다. 같은 정책이라도 어느 모델이 맞느냐에 따라 “리뷰어 추가”의 가치가 달라진다. 지금의 Orbit 기록은 delay 모델을 지지하지만, 그 결론은 지금의 부하에서만 확인한 것이다. 부하가 늘 때 어느 쪽인지 가리려면 리뷰를 시작한 시각이 필요하다. GitHub에서는 리뷰 요청(review_requested) 사건의 시각과 리뷰어가 남긴 첫 댓글의 시각을 모으면, 요청부터 첫 반응까지와 첫 반응부터 리뷰 제출까지를 나눠 어림할 수 있다.

막히면
증상확인과 해결
오류: simpy와 numpy가 필요하다python3 -m pip install simpy. 이 교안은 SimPy 4.1.2로 실행했다
숫자가 교안과 조금 다르다씨앗값(--seed)과 반복 수(--reps)가 같은지 본다. 둘이 같으면 같은 결과가 나온다. 반복 수를 줄이면 범위가 넓어지고 평균이 흔들린다
오류: 추정이나 재생에 쓸 PR이 너무 적다추정 기간의 PR이 20건보다 적다. --from, --to를 넓힌다. PR이 적으면 리뷰어별 지연 분포를 믿기 어렵다
처리 시간 열이 -다--load를 1이 아닌 값으로 주면 실제 업무와 짝이 없는 도착을 만들기 때문에 처리 시간을 계산하지 않는다
org-mining.py의 넘김 표가 비어 있다resource 열이 비어 있다. 실습 22의 log-check.sh로 수행자가 빈 사건 수를 확인한다
  • M/M/c 표에서 이용률과 창구 수가 대기에 주는 영향을 설명할 수 있다.
  • queue-mining.py로 도착률, 리뷰어별 L = λW, 묶음 리뷰 비율을 확인했다.
  • 앞에 기다린 PR 수별 대기를 M/M/1 예측과 비교해, 리뷰 대기가 처리 능력보다 응답 지연에서 왔다는 근거를 적었다.
  • 역할 발견과 작성자 → 첫 리뷰어 표에서 리뷰 집중을 확인하고, 결정 나무의 두 분기를 읽었다.
  • delay 모델과 queue 모델을 3~5월로 검증하고, 정책 네 가지를 비교해 whatif.sh의 어림과 맞댔다.
  • 6~9월 재생 결과가 범위 밖에 나온 이유 두 가지를 적었다.

마치며...

대기열 이론은 리뷰어가 바빠서 생긴 대기라면 창구를 늘리라고 말하지만, Orbit의 리뷰 대기는 줄의 길이와 거의 무관했다. 그래서 리뷰어 추가보다 배정 방식을 바꾸는 정책이 대기를 더 줄였고, 시뮬레이션은 이 판단에 범위를 붙이고 어림이 다루지 못하는 정책을 비교하게 해 주었다. 다른 기간으로 검증하자 모델은 리뷰 당번의 효과를 작게 예측했고, 그 차이에서 사람의 응답 속도가 정책과 함께 바뀐다는 가정이 빠졌음을 찾았다. 다음 모듈에서는 지금 열려 있는 업무가 늦어질지를 미리 예측하고, 그 예측을 경보로 운영할 때의 문제를 다룬다.