cd ~/gh-pm-lab source pm-lab/env.sh
1분포를 분위수로 읽기
A1에서 처리 시간의 정의로 고른 L4(작업 시작 신호 → 마지막 업무 완료, 업무 시간)의 분포를 다시 본다.
bash github-pm-starter-kit/duration.sh pm-lab/orbit-event-log-clean.csv --start "작업 시작,첫 커밋,막힘" --clock business | tail -1
- 평균 35h, 중앙값 33h, 85% 53.9h, 95% 64.7h, 최소 0.4h, 최대 84h
duration.sh가 계산한 중앙값, 평균, 85%, 95%다.업무 처리 시간의 분포는 대개 오른쪽으로 꼬리가 길다. 처리 시간은 0보다 짧을 수 없지만, 막힘이나 재작업이 겹친 업무는 몇 배로 길어질 수 있기 때문이다. 오른쪽 꼬리가 길면 평균이 중앙값보다 커진다. Orbit 로그에서는 평균 35h가 중앙값 33h보다 2h 크고, 가장 긴 업무는 84h로 중앙값의 2.5배다. 바칸티(D. Vacanti)는 『Actionable Agile Metrics for Predictability』(2015)에서 평균과 표준편차 대신 분위수로 처리 시간을 말하라고 권한다. “85%가 53.9h 안에 끝났다”는 문장은 분포의 모양을 가정하지 않고, 업무 100건 중 85건에 대한 사실을 말한다. 일정을 약속할 때 85%를 쓴 것(실습 15)도 같은 이유다.
2사이클 타임 산점도
분위수 하나는 기간 전체를 한 숫자로 줄인다. 바칸티의 사이클 타임 산점도(cycle time scatterplot)는 끝난 업무 하나를 점 하나로, 가로축에 완료 날짜, 세로축에 처리 시간을 찍고, 분위수를 가로선으로 긋는다. 시간에 따라 점의 높이가 달라지는지, 선 위로 튀어 오른 업무가 언제 몰렸는지 보인다.
6월 1일 뒤에 끝난 업무의 점이 조금 낮게 모여 있지만, 그림만으로는 차이의 크기를 말하기 어렵다. 작업 시작 날짜로 기간을 나눠 같은 분위수를 계산한다.
bash github-pm-starter-kit/duration.sh pm-lab/orbit-event-log-clean.csv --start "작업 시작,첫 커밋,막힘" --clock business --to 2026-05-31 | tail -1 bash github-pm-starter-kit/duration.sh pm-lab/orbit-event-log-clean.csv --start "작업 시작,첫 커밋,막힘" --clock business --from 2026-06-01 | tail -1
- 평균 39.1h, 중앙값 36.8h, 85% 56.2h, 95% 65.2h, 최소 5.6h, 최대 83.5h - 평균 31.1h, 중앙값 27.7h, 85% 48.8h, 95% 62.7h, 최소 0.4h, 최대 84h
중앙값은 36.8h에서 27.7h로, 85%는 56.2h에서 48.8h로 줄었고, 95%는 65.2h에서 62.7h로 거의 그대로다. 두 기간의 숫자를 나란히 놓는 것은 실습 27의 compare.sh --split과 같은 비교다. 이 비교는 “기간 안에서 숫자가 안정되어 있었다”는 가정 위에 서 있다. 3~5월 안에서 이미 숫자가 계속 움직이고 있었다면, 6월 1일 전후의 차이는 그 움직임의 연장일 수 있다. 기간 안의 움직임을 판정하는 도구가 관리도다.
3변동의 두 종류와 XmR 관리도
슈하트(W. A. Shewhart)는 『Economic Control of Quality of Manufactured Product』(1931)에서 공정의 변동을 두 종류로 나눴다. 늘 작용하는 많은 작은 원인이 만드는 변동과, 찾아내 없앨 수 있는 특정한 원인이 만드는 변동이다. 데밍은 앞의 것을 일반 원인(common cause), 뒤의 것을 특수 원인(special cause)이라고 불렀다. 일반 원인의 변동만 있는 공정은 예측할 수 있고, 그 흔들림 하나하나에 원인을 찾는 것은 시간을 쓰고 공정을 더 흔든다. 통계적 공정 관리(Statistical Process Control, SPC)는 데이터로 두 종류를 가르는 방법이다.
관리도는 시간 순서로 찍은 값에 중심선과 한계선을 그어 두 종류의 변동을 가르는 그림이다. 주별 값처럼 한 기간에 값이 하나인 자료에는 개별값-이동범위(individuals and moving range, XmR) 관리도를 쓴다. 휠러(D. J. Wheeler)는 『Understanding Variation』(1993)에서 이 관리도를 공정 행동 차트(process behavior chart)라고 부르고, 관리 현장 밖의 숫자(매출, 처리량, 대기 시간)에 쓰는 법을 정리했다.
| 값 | 계산 |
|---|---|
| 중심선 | 값의 평균 X̄ |
| 이동범위 mR | 이웃한 두 값의 차이의 절댓값. 값이 n개면 n−1개 |
| 자연 공정 한계(natural process limits) | X̄ ± 2.66 × 평균 이동범위. 2.66은 3 ÷ 1.128이고, 1.128은 두 값의 범위로 표준편차를 어림하는 상수다 |
| 이동범위 상한 | 3.268 × 평균 이동범위 |
표준편차 대신 이동범위를 쓰는 이유는, 값들 사이에 큰 변화가 섞여 있어도 이웃한 두 값의 차이는 그 변화의 영향을 덜 받기 때문이다. 키트의 xmr.sh가 이 계산과 신호 판정을 한다. 먼저 리드 문단의 질문에 답한다. 실습 15의 처리량 목록을 그대로 넣는다.
자연 공정 한계는 0~6.7건이고, 이동범위 상한은 5.6건이다. 처리량이 0건인 주도, 지난주보다 2건 적은 주도 이 공정에서 늘 나올 수 있는 값이다. 한 주에 7건 이상을 끝내거나, 이웃한 두 주의 차이가 6건 이상이 되어야 신호다. 같은 8주를 보고 “이번 주 처리량이 줄었다”고 원인을 찾는 회의를 하면, 일반 원인의 흔들림을 설명하는 데 시간을 쓴다.
4Orbit 주별 처리량
로그 파일을 주면 xmr.sh가 주별 값을 계산한다. 주는 월요일에 시작하고, 처리량은 마지막 업무 완료가 그 주에 있는 사례 수다.
27주 중 신호는 3월 2일 주 하나다. 기록이 시작된 첫 주라 작업을 시작한 업무가 아직 끝나지 않아 1건만 끝났다. 원인이 분명한 특수 원인이다. 나머지 26주는 2.2~16.9건의 한계 안에 있고, 6월 1일 전후에도 신호가 없다. Orbit 팀은 매주 업무 10건을 만들고(2단계 안내), 만든 업무가 모두 끝난다면 오랜 기간 평균으로 끝나는 업무 수는 만드는 업무 수와 같아진다. 리뷰 당번이 바꾼 것은 업무가 끝날 때까지 걸린 시간이고, 한 주에 끝나는 업무 수는 들어오는 업무 수가 정한다.
5첫 리뷰 대기와 처리 시간
리뷰 당번이 직접 겨냥한 첫 리뷰 대기를 본다. 주별 값은 그 주에 만든 PR의 첫 리뷰 대기 중앙값이다. 먼저 모든 주로 한계를 계산한다.
6월 1일 뒤의 값은 대부분 1~3h인데, 모든 주로 계산한 중심선이 4.3h라 한계 밖으로 나간 점이 없다. 바뀐 공정의 값과 바뀌기 전의 값이 한 번에 평균과 이동범위에 섞여 한계가 넓어졌기 때문이다. 변화는 7월 27일 주에야 “중심선 아래 연속 8점”으로 잡힌다. 3월 2일 주는 PR이 1건이라 값 하나가 그 주를 대표하지 못한다.
휠러는 바뀌기 전의 안정된 기간으로 한계를 계산하고, 그 뒤의 값을 그 한계에 비춰 보라고 권한다. PR이 1건뿐인 3월 2일 주를 빼고(--from), 5월 마지막 주까지로 한계를 계산한다(--baseline-to).
xmr.sh --tsv 출력으로 그림). 음영은 한계 계산 기간, 주황 테두리는 신호가 난 주, 점선은 2시그마 선(중심선 − 1.773 × 평균 이동범위)이다. 하한은 0 이하라 없다.3~5월 12주로 계산한 중심선은 6.8h, 상한은 13.7h다. 6월 8일 주에 1.2h로 떨어졌지만 하한이 없으므로 그 한 점은 신호가 아니다. 6월 22일 주에 “연속 3점 중 2점이 2시그마 선(2.2h) 아래”(규칙 2)가 처음 나오고, 7월 27일 주부터는 중심선 아래 연속 8점(규칙 3)이 이어진다. 6월 첫 주(7.6h)는 오히려 높다. 그 주에 만든 PR을 열어 본다.
for w in 2026-03-16 2026-06-01; do
jq -R -s -r -L github-pm-starter-kit/lib --arg w "$w" 'include "eventlog";
($w + "T00:00:00Z" | fromdate) as $a | parse_log | cases_of
| map(work_start as $ws | first_reviews[] | . + {ws: $ws}) | map(select(.from >= $a and .from < $a + 7 * 86400))
| "\($w) 주: PR \(length)건, 6월 1일 전에 작업을 시작한 업무의 PR \(map(select(.ws < ("2026-06-01T00:00:00Z" | fromdate))) | length)건, 리뷰어별 첫 리뷰 수 \(group_by(.who) | map(length) | sort | reverse | map(tostring) | join(", "))"' \
pm-lab/orbit-event-log-clean.csv
done
2026-03-16 주: PR 10건, 6월 1일 전에 작업을 시작한 업무의 PR 10건, 리뷰어별 첫 리뷰 수 8, 2 2026-06-01 주: PR 11건, 6월 1일 전에 작업을 시작한 업무의 PR 9건, 리뷰어별 첫 리뷰 수 6, 2, 1, 1, 1
6월 1일 주에 만든 PR 11건 중 9건은 5월에 작업을 시작한 업무의 PR이고, 첫 리뷰 11건 중 6건을 한 사람이 맡았다. 이 기록은 리뷰 당번이 작업 시작이 6월 1일 이후인 업무부터 적용되었다는 해석과 맞고, 2단계가 기간을 작업 시작 날짜로 나눈 것(실습 26, 27)도 같은 기준이다. 3월 16일 주의 한계 밖 점(13.9h)도 같은 모양이다. PR 10건 중 8건의 첫 리뷰를 한 사람이 맡았다. 기준 기간 안의 특수 원인이 한계를 넓혔으므로, 실제 팀이라면 그 주의 원인을 기록하고 그 주를 빼고 한계를 다시 계산할지 정한다.
변화가 확인되면 바뀐 공정의 한계를 새로 계산한다. 6월 8일 주부터의 13주만 쓴다.
bash github-pm-starter-kit/xmr.sh pm-lab/orbit-event-log-clean.csv --series review --from 2026-06-08 | sed -n '3,6p;$p'
- 자료: pm-lab/orbit-event-log-clean.csv, 주 13개 중 값이 있는 주 13개 - 한계 계산: 모든 값 13개 - 중심선(평균) 2h, 평균 이동범위 1.2h - 자연 공정 한계 없음 ~ 5.1h (하한 계산값이 0 이하(-1.2h)라 하한을 두지 않는다), 이동범위 상한 3.9h 신호가 없다. 값의 오르내림은 모두 자연 공정 한계 안의 흔들림이다.
새 공정의 중심선은 2h, 상한은 5.1h이고, 13주 안에 신호가 없다. 앞으로 첫 리뷰 대기 중앙값이 한 주라도 5.1h를 넘으면, 그것은 리뷰 당번이 흔들리고 있다는 신호다.
처리 시간(--series lead)도 같은 두 방법으로 본다. 처리 시간은 그 주에 끝난 업무로 묶으므로, 끝난 업무가 1건과 4건뿐인 3월 첫 두 주를 뺀다.
| 지표 | 한계 계산 | 중심선 | 한계 | 6월 뒤 첫 신호 |
|---|---|---|---|---|
| 처리량 | 모든 주 | 9.5건 | 2.2~16.9건 | 없음 |
| 첫 리뷰 대기 | 모든 주 | 4.3h | ~10.4h | 7월 27일 주 (규칙 3) |
| 첫 리뷰 대기 | 3월 9일~5월 25일 | 6.8h | ~13.7h | 6월 22일 주 (규칙 2) |
| 처리 시간 | 모든 주 | 33.2h | 10.7~55.6h | 없음 |
| 처리 시간 | 3월 16일~5월 25일 | 38.6h | 18.6~58.6h | 7월 13일 주 (규칙 2), 7월 20일 주 (규칙 1) |
6월 1일의 변화는 첫 리뷰 대기에서 3주 만에, 처리 시간에서 6주 만에 신호로 나타났다. 바뀐 공정의 값을 한계 계산에 섞으면 처리 시간에서는 신호가 아예 나오지 않는다. 처리 시간은 리뷰 대기 말고도 개발, 막힘, 재작업이 합쳐진 값이라, 리뷰 대기가 줄어든 몫이 다른 단계의 흔들림에 묻힌다. 효과를 확인하려는 개선이 있으면, 그 개선이 직접 바꾸는 지표(여기서는 첫 리뷰 대기)의 관리도를 먼저 본다.
6신호 규칙 정하기
웨스턴 일렉트릭(Western Electric)사는 1956년 『Statistical Quality Control Handbook』에서 관리도를 현장마다 같은 방식으로 읽도록 판정 규칙을 정했다. 중심선에서 1, 2, 3시그마 떨어진 선으로 구역을 나누고, 점이 어느 구역에 어떤 순서로 찍혔는지로 신호를 판정한다. xmr.sh는 그중 세 규칙과 이동범위 규칙을 쓴다.
| 규칙 | 판정 | 잡아내는 변화 |
|---|---|---|
| 규칙 1 | 한 점이 자연 공정 한계 밖 | 크고 갑작스러운 변화. 한 주에 일어난 일 |
| 규칙 2 | 연속 3점 중 2점이 중심선 한쪽의 2시그마 선 밖 | 중간 크기의 변화가 이어질 때 |
| 규칙 3 | 중심선 한쪽에 연속 8점 | 작지만 계속되는 변화. 8주가 지나야 잡힌다 |
| mR | 이동범위가 상한 밖 | 이웃한 두 주 사이의 급한 변화 |
규칙을 더할수록 실제 변화를 빨리 잡지만, 일반 원인만 있는데도 신호가 나는 경우(거짓 경보)도 늘어난다. 규칙은 결과를 보고 고르지 않고, 관리도를 쓰기 전에 팀이 정해 둔다. 아래는 Orbit 팀이 정할 만한 판정 규칙의 예다.
**신호 판정 규칙** (2026-09-07 정함) - 관리도: 주별 처리량, 주별 첫 리뷰 대기 중앙값. 매주 월요일 process-report 워크플로가 계산한다 - 한계: 첫 리뷰 대기는 2026-06-08 ~ 08-31의 13주(중심선 2h, 상한 5.1h), 처리량은 2026-03-09 ~ 08-31의 26주 - 신호: 규칙 1, 규칙 2, 규칙 3, 이동범위 상한. 그 밖의 오르내림은 보고서에 숫자만 적고 원인을 찾지 않는다 - 신호가 나면: 그 주의 PR과 업무를 열어 원인을 기록하고, 주간 보고 맨 위에 “신호: 지표, 주, 규칙, 확인한 원인”을 적는다 - 한계를 다시 계산하는 때: 원인을 확인한 변화가 8주 이상 이어졌을 때, 또는 팀 구성이나 업무 종류가 바뀌었을 때. 다시 계산한 날과 이유를 이 파일에 적는다 - 주의: 한 주의 PR이 3건보다 적으면 그 주의 중앙값으로 신호를 판정하지 않는다
7리틀의 법칙이 맞는 조건
실습 15에서 평균 진행 중 업무 수(L) = 평균 처리율(λ) × 평균 처리 시간(W)이라는 리틀의 법칙(Little's law)을 썼다. 리틀(J. D. C. Little)이 1961년에 증명한 이 식은 오랜 기간의 평균 사이의 관계다. 정해진 몇 주 안에서 쓰려면 조건이 필요하다. 바칸티는 들어온 수와 나간 수가 같고, 시작한 업무가 결국 끝나고, 기간 처음과 끝의 진행 중 업무 수가 비슷하고, 진행 중 업무의 평균 나이가 늘거나 줄지 않고, 세 값의 시간 단위가 같아야 한다고 정리했다. 키트의 little.sh는 기간을 받아 세 값과 조건을 함께 출력한다. 기록이 시작된 첫 5주, 3~5월, 6~8월을 비교한다.
첫 5주는 시작이 43건, 끝남이 33건이고, 진행 중 업무가 0건에서 10건으로 늘었다. 업무가 쌓이는 중이라 λ × W(6.7건)가 실제 평균 진행 중 업무(7.9건)보다 15% 작다. 6~8월은 시작 129건, 끝남 135건으로 비슷하고, λ × W(8.3건)와 L(8건)의 차이가 4%다. 같은 식이 흐름이 쌓이는 기간에는 맞지 않고, 안정된 기간에는 맞는다.
3~5월은 첫 5주를 포함해 시작 126건, 끝남 115건이고 차이가 3%다. 3~5월과 6~8월을 비교하면, 들어온 업무는 126건과 129건으로 비슷하고 W는 6.5일에서 5.5일로, L은 8.6건에서 8건으로 줄었다. λ가 1.29건/일에서 1.52건/일로 커진 것은 3~5월이 업무가 쌓이는 첫 5주를 포함하기 때문이다.
식을 쓸 때 두 가지를 확인한다. 첫째, W는 평균이다. 중앙값이나 85%를 넣으면 식이 맞지 않는다. 둘째, 단위를 맞춘다. λ를 달력 일 기준(건/일)으로 쟀으면 W도 달력 일이어야 하고, 업무 시간으로 잰 처리 시간(L4)을 넣으면 안 된다.
8주간 보고에 신호 올리기
관리도는 매주 같은 규칙으로 판정해야 쓸모가 있다. xmr.sh 출력의 마지막 줄이 판정 결과다.
bash github-pm-starter-kit/xmr.sh pm-lab/orbit-event-log-clean.csv --series review --from 2026-03-09 --baseline-to 2026-05-31 | tail -1 bash github-pm-starter-kit/xmr.sh pm-lab/orbit-event-log-clean.csv --series throughput --from 2026-03-09 | tail -1
신호가 난 주 10개: 2026-03-16, 2026-06-22, 2026-07-06, 2026-07-20, 2026-07-27, 2026-08-03, 2026-08-10, 2026-08-17, 2026-08-24, 2026-08-31 신호가 없다. 값의 오르내림은 모두 자연 공정 한계 안의 흔들림이다.
실습 29에서 설치한 process-report 워크플로가 매주 만드는 보고서 맨 앞에 이 두 줄을 넣는다. 학습자 저장소의 .github/pm-kit/에 xmr.sh를 복사하고, 워크플로의 “이벤트 로그와 보고서 만들기” 단계에서 보고서를 쓰는 묶음을 아래처럼 바꾼다. --from과 --baseline-to에는 신호 판정 규칙 파일에 적은 한계 계산 기간을 쓴다. 아래 날짜는 Orbit의 예다.
{
echo "## 관리도 신호"
echo
echo "- 첫 리뷰 대기: $(bash "$kit/xmr.sh" pm-data/logs/event-log-clean.csv --series review \
--from 2026-06-08 --baseline-to 2026-08-31 | tail -1)"
echo "- 처리량: $(bash "$kit/xmr.sh" pm-data/logs/event-log-clean.csv --series throughput \
--from 2026-03-09 --baseline-to 2026-08-31 | tail -1)"
echo
bash "$kit/log-check.sh" pm-data/logs/event-log.csv
echo
bash "$kit/process-map.sh" pm-data/logs/event-log-clean.csv --rules "$kit/process-rules.tsv"
echo
bash "$kit/bottleneck.sh" pm-data/logs/event-log-clean.csv --roles "$kit/roles.tsv"
} > "pm-data/reports/$d.md"
공개 저장소로 연습하려면 실습 15의 flow-metrics.sh로 26주 처리량을 받아 넣는다. GitHub에 접속하는 명령이라 교안에는 출력을 싣지 않았다. 결과는 실행한 날에 따라 다르다.
tp=$(bash github-pm-starter-kit/flow-metrics.sh cli/cli --weeks 26 | tail -1 | sed 's/^--throughput //') bash github-pm-starter-kit/xmr.sh --values "$tp"
막히면
| 증상 | 확인과 해결 |
|---|---|
오류: 값이 있는 주가 3개보다 적다 | --from, --to 기간이 너무 짧거나 그 기간에 PR이 없다. 값이 적으면 한계가 넓고, 값이 하나 더해질 때마다 크게 움직인다. 값이 쌓이면 한계를 다시 계산한다 |
| 표에 “값 없음”인 주가 있다 | 그 주에 만든 PR(리뷰)이나 끝난 업무(처리 시간)가 없다. 그 주는 이동범위 계산에서 건너뛴다 |
| 하한이 “없음”이다 | 계산한 하한이 0 이하다. 대기 시간과 건수는 0보다 작을 수 없으므로 하한 밖 신호는 없고, 아래쪽 변화는 규칙 2와 규칙 3으로 잡는다 |
오류: --values는 쉼표로 나눈 0 이상의 수 목록이다 | flow-metrics.sh의 마지막 줄에서 --throughput 부분을 빼고 넣었는지 확인한다 |
jq: error: ... fromdate | jq 1.6 이상인지 jq --version으로 확인한다 |
- 처리 시간 분포에서 평균과 중앙값이 다른 이유를 오른쪽 꼬리로 설명할 수 있다.
- 사이클 타임 산점도에서 분위수 선과 6월 1일 선을 읽었다.
- Launchpad 8주 처리량의 자연 공정 한계를 계산하고, 리드 문단의 질문에 답했다.
- Orbit 첫 리뷰 대기에서 한계를 모든 주로 계산할 때와 3~5월로 계산할 때 신호가 언제 나오는지 비교했다.
- 6월 1일 주와 3월 16일 주의 값을 PR 단위로 열어 원인을 확인했다.
little.sh로 리틀의 법칙이 맞는 기간과 맞지 않는 기간을 구분했다.- 팀의 신호 판정 규칙을
pm-lab/signal-rules.md에 적었다.
마치며...
Launchpad 8주와 Orbit 27주의 처리량은 기록 첫 주 하나를 빼면 모두 자연 공정 한계 안에서 오르내렸다. 특수 원인은 한계와 미리 정한 신호 규칙으로 가려낸다. Launchpad의 처리량 0건은 신호가 아니었고, Orbit의 리뷰 당번은 3~5월로 계산한 한계에 비춰 첫 리뷰 대기에서 3주 만에 신호로 나타났다. 한계를 바뀐 기간까지 섞어 계산하면 같은 변화를 늦게 보거나 놓쳤다. 다음 모듈에서는 안정된 기간의 처리량으로 마일스톤 완료 날짜를 예측하고, 지난 예측이 얼마나 맞았는지 기록으로 보정한다.