소프트웨어 공학 100 주제 시리즈의 77번째 글이다. (카테고리: 프로젝트 관리와 추정)

한 줄 요약

팀이 과거에 실제로 끝낸 작업 개수(처리량) 를 무작위로 다시 뽑아 미래를 수천 번 모의 실행하면, “언제 끝나나?” 에 날짜 하나가 아니라 확률 분포로 답할 수 있다. 작업마다 추정을 하지 않아도 되고, 결과가 “85% 확률로 12주 이내” 같은 형태로 나온다.

왜 필요한가

전통적인 예측은 이렇다. 남은 작업을 하나하나 추정해 더하고, 팀의 속도로 나눈다. 여기에는 세 가지 문제가 있다.

  1. 추정 비용. 백로그 수십 개를 하나하나 추정하는 데 시간이 든다. 먼 미래의 항목은 어차피 쪼개지고 바뀐다.
  2. 평균의 함정. “평균 속도로 나눈 값” 은 대략 50% 확률의 답이다. 절반은 늦는다는 뜻인데, 듣는 사람은 그 숫자를 약속으로 받아들인다.
  3. 변동성의 무시. 휴가, 장애 대응, 예상 못 한 의존성 때문에 주마다 처리량이 크게 출렁인다. 평균은 이 출렁임을 지운다.

몬테카를로 예측은 세 문제를 함께 다룬다. 추정 대신 과거 실측 데이터를 쓰고, 평균 대신 분포를 내며, 변동성을 그대로 시뮬레이션에 넣는다.

핵심 개념

몬테카를로 방법

몬테카를로 방법은 확률적 문제를 무작위 표본을 대량으로 만들어 근사하는 기법이다. Nicholas Metropolis 와 Stanislaw Ulam 의 1949년 논문 “The Monte Carlo Method”(Journal of the American Statistical Association)이 이 이름을 제목으로 내건 고전이다. 확률 분포와 기댓값의 기초는 확률 분포 글을 보라.

일정 예측에 적용하면 구조는 단순하다.

과거 처리량 표본          한 번의 시도(trial)                    수천 번 반복
[3,5,2,6,4,0,5,...]  →  주1: 무작위로 5 뽑음 → 남은 35       →  "11주" "9주" "13주" ...
                         주2: 무작위로 2 뽑음 → 남은 33              ↓
                         ...  남은 ≤ 0 이 된 주 = 이번 시도 결과    누적 분포 → 백분위수

과거 표본에서 복원 추출하는 이 방식은 통계학의 부트스트랩과 같은 발상이다. 처리량 분포를 정규분포 같은 모양으로 가정할 필요가 없다.

왜 처리량인가 — 흐름 지표

칸반 가이드(2025년 5월판, John Coleman·Daniel Vacanti)는 흐름 지표 네 가지를 정의한다.

지표 정의
WIP 시작했지만 끝나지 않은 작업 항목 수
처리량(Throughput) 단위 시간당 끝난 작업 항목 수. 정확한 개수로 센다
작업 항목 나이(Work Item Age) 시작부터 현재까지 경과 시간
사이클 타임(Cycle Time) 시작부터 완료까지 경과 시간

처리량은 포인트가 아니라 개수다. 그래서 측정이 싸고 조작하기 어렵다. 같은 가이드는 서비스 수준 기대치(SLE) 를 “작업 항목 하나가 시작부터 완료까지 얼마나 걸려야 하는지에 대한 예측” 으로 정의한다. SLE 는 경과 시간과 확률 두 부분으로 이루어진다. 가이드의 예는 “작업 항목의 85% 가 8일 이내에 끝난다” 이다. 몬테카를로 예측은 이 사고방식을 “작업 하나” 에서 “작업 묶음” 으로 넓힌 것이다.

WIP, 처리량, 사이클 타임 사이에는 리틀의 법칙(L = λW, John Little 의 1961년 증명)이 성립한다. 이 관계와 WIP 제한은 SE100 #064 에서 다룬다. 몬테카를로 예측에서는 리틀의 법칙이 하나의 실무 조건으로 나타난다. 과거와 미래의 시스템이 비슷해야 한다. WIP 가 갑자기 두 배가 되면 과거 처리량은 미래를 대표하지 못한다.

예제

지난 12주의 주간 처리량과 남은 백로그 40개로 예측해 보자. 표준 라이브러리 random만 쓴다.

import random
from collections import Counter

weekly_throughput = [3, 5, 2, 6, 4, 0, 5, 7, 3, 4, 6, 2]   # 지난 12주 완료 개수
backlog = 40
TRIALS = 20_000
random.seed(42)

def weeks_to_finish(remaining):
    weeks = 0
    while remaining > 0:
        remaining -= random.choice(weekly_throughput)   # 과거 한 주를 복원 추출
        weeks += 1
    return weeks

results = sorted(weeks_to_finish(backlog) for _ in range(TRIALS))
def pct(p):  # 시도의 p% 가 이 주 수 안에 끝났다
    return results[int(p / 100 * TRIALS) - 1]

print("평균 처리량으로 나눈 단일 값:", round(backlog / (sum(weekly_throughput) / 12), 1), "주")
for p in (50, 70, 85, 95):
    print(f"{p}% 확률로 {pct(p)}주 이내")
cum = 0
for w, n in sorted(Counter(results).items()):
    cum += n
    if 8 <= w <= 15:
        print(f"  {w:2d}주 이내 완료 확률 {cum / TRIALS:6.1%}")

# 반대 질문: 8주 뒤까지 몇 개를 끝낼 수 있나?
done = sorted(sum(random.choice(weekly_throughput) for _ in range(8)) for _ in range(TRIALS))
print(f"8주 동안 85% 확률로 최소 {done[int(0.15 * TRIALS)]}개 완료")

실행 결과:

평균 처리량으로 나눈 단일 값: 10.2 주
50% 확률로 11주 이내
70% 확률로 11주 이내
85% 확률로 12주 이내
95% 확률로 14주 이내
   8주 이내 완료 확률   6.6%
   9주 이내 완료 확률  24.2%
  10주 이내 완료 확률  49.2%
  11주 이내 완료 확률  72.1%
  12주 이내 완료 확률  87.1%
  13주 이내 완료 확률  95.0%
  14주 이내 완료 확률  98.3%
  15주 이내 완료 확률  99.5%
8주 동안 85% 확률로 최소 26개 완료

읽는 법:

  1. 평균으로 나눈 10.2주는 동전 던지기다. 10주 이내 완료 확률은 49% 에 불과하다.
  2. 확률을 고르는 것은 비즈니스 결정이다. 마케팅 행사 날짜처럼 놓치면 큰일 나는 약속이라면 95%(13~14주)를, 내부 목표라면 70~85% 를 고른다. 개발자가 아니라 위험을 지는 사람이 고른다.
  3. 두 가지 질문에 답할 수 있다. “이 범위는 언제 끝나나?(When)” 와 “이 날짜까지 몇 개를 하나?(How many)”. 두 번째는 날짜가 고정된 상황(SE100 #079)에서 특히 쓸모 있다.

실무 적용

데이터 준비 체크리스트

  • 완료의 정의를 하나로. “배포됨” 기준으로 셀지 “리뷰 끝남” 으로 셀지 정하고 바꾸지 않는다.
  • 최근 데이터를 쓴다. 팀 구성, 기술 스택, 프로세스가 바뀌기 전의 데이터는 버린다. 예제처럼 최근 십여 주의 데이터로 시작하고, 쌓이는 대로 갱신한다.
  • 0 인 주도 포함한다. 장애나 휴가로 아무것도 못 끝낸 주는 미래에도 온다.
  • 크기가 극단적인 항목은 쪼갠다. 처리량 방식은 항목 크기가 “대체로 비슷하다” 고 가정한다. 다른 항목의 10배짜리 에픽이 섞여 있으면 먼저 쪼갠다.

백로그 자체의 불확실성

백로그 40개는 고정값이 아니다. 진행하면서 쪼개지고, 새로 발견된다. 단순한 처리 방법:

# 백로그 성장 반영: 남은 항목 수 자체를 범위로 두고 시도마다 뽑는다
def trial():
    remaining = random.randint(40, 55)   # 쪼개짐·발견으로 40~55개가 될 수 있다
    return weeks_to_finish(remaining)

과거 프로젝트에서 “처음 백로그 대비 최종 완료 개수” 비율을 기록해 두면 이 범위를 근거 있게 정할 수 있다.

매주 다시 돌린다

예측은 한 번 내고 끝내는 문서가 아니다. 매주 새 처리량을 넣고 다시 돌리면 분포가 이동하는 것이 보인다. 85% 선이 3주 연속 뒤로 밀리면 그것이 범위를 줄이거나 날짜를 옮기자는 신호다. 진척 추적과의 연결은 SE100 #080 에서 다룬다.

흔한 오해와 함정

  • “추정이 필요 없으니 계획도 필요 없다.” 처리량 예측은 개별 항목 추정을 대신할 뿐이다. 항목을 비슷한 크기로 자르는 일, 즉 백로그 정제는 여전히 필요하다.
  • “시뮬레이션이니 정확하다.” 입력이 과거 데이터이므로 미래가 과거와 비슷할 때만 유효하다. 팀원 절반이 바뀌거나 전혀 다른 종류의 일을 시작하면 과거 표본은 의미를 잃는다.
  • “시도 횟수를 늘리면 불확실성이 준다.” 시도를 늘리면 시뮬레이션의 표본 오차가 줄 뿐, 미래 자체의 불확실성은 줄지 않는다. 몇천 번이면 충분하다.
  • “85% 면 늦을 일이 없다.” 대략 일곱 번에 한 번은 그 선을 넘는다. 확률을 말하는 것은 늦을 수 있음을 미리 합의하는 일이다.
  • “주별 처리량은 서로 독립이다.” 실제로는 장애가 난 주 다음 주도 느린 식의 상관이 있을 수 있다. 상관이 강하다고 의심되면 개별 주 대신 연속된 몇 주 묶음을 뽑는 식으로 보완한다.

확인 문제

  1. “평균 처리량으로 나눈 기간” 이 대략 몇 % 확률의 답인지, 예제 결과로 설명하라.
  2. 칸반 가이드가 정의하는 처리량의 측정 단위는 무엇이며, 포인트보다 나은 점은?
  3. 예측에서 85% 와 95% 중 무엇을 쓸지는 누가 정해야 하는가?
  4. 처리량 기반 몬테카를로 예측이 유효하려면 어떤 조건이 필요한가?
  5. 시도 횟수를 2만 번에서 200만 번으로 늘리면 예측이 더 믿을 만해지는가?

풀이

  1. 약 50%. 예제에서 평균으로 나눈 값은 10.2주인데, 시뮬레이션에서 10주 이내 완료 확률은 49.2% 였다.
  2. 완료된 작업 항목의 정확한 개수. 측정이 싸고, 추정 단위처럼 부풀리거나 팀마다 다르게 해석할 여지가 적다.
  3. 일정을 놓쳤을 때 위험과 비용을 지는 비즈니스 쪽 의사결정자. 개발 팀은 확률별 날짜를 보여 주는 역할이다.
  4. 미래의 작업 시스템(팀, 작업 종류, 완료 정의, WIP 수준)이 과거 데이터를 모은 시기와 비슷해야 하고, 항목 크기가 극단적으로 다르지 않아야 한다.
  5. 아니다. 시뮬레이션 자체의 표본 오차만 줄어든다. 입력 데이터가 미래를 대표하지 못하는 문제는 그대로다.

더 읽을거리 (References)