Workspace IndexAlgorithms › Day 49

Capacity Planning, SLOs, and Error Budgets TODO

Algorithms · Day 49 / 100 · C. Concurrency & Performance Engineering (Day 36-51)

Concept

An SLO is a target set on a service-level indicator (SLI) measured from the user's perspective, and the error budget is the total amount of failure that target allows. If the availability target is set at 99.9%, the remaining 0.1% is the budget available for that period, and it becomes an explicit trade-off mechanism between shipping speed and stability. When budget remains you can deploy more aggressively; once it's exhausted, the policy is to halt feature releases and put the effort into reliability work instead. Capacity planning combines this with load forecasting to determine the maximum load the system can absorb while holding the target latency, and how much headroom is needed. From a queuing-theory perspective, wait time diverges sharply as utilization approaches 1, so headroom should be sized against peak and tail load, not average utilization.

Without agreeing on "how stable does this need to be" as a number, every priority fight between incident response and feature work turns into an emotional argument.

Code & Formula

# 용량 계획·SLO와 에러 예산 — 가용성 목표에서 허용 실패량(에러 예산)을 계산하고,
# 실측 실패율로 예산 소진율을 구해 배포를 계속할지 판단한다.

def error_budget_minutes(slo_percent, period_days=30):
    """기간 동안 허용되는 다운타임(분)"""
    period_minutes = period_days * 24 * 60
    allowed_failure_ratio = 1 - slo_percent / 100
    return period_minutes * allowed_failure_ratio

def budget_status(slo_percent, downtime_minutes_so_far, days_elapsed, period_days=30):
    total_budget = error_budget_minutes(slo_percent, period_days)
    consumed_ratio = downtime_minutes_so_far / total_budget
    # 지금까지 경과한 기간 대비 정상 소진 속도(1.0이면 딱 예산대로 소진 중)
    expected_ratio_by_now = days_elapsed / period_days
    burn_rate = consumed_ratio / expected_ratio_by_now if expected_ratio_by_now else 0
    return total_budget, consumed_ratio, burn_rate

slo = 99.9  # 월 가용성 목표
total_budget = error_budget_minutes(slo)
print(f"SLO {slo}% -> 30일 에러 예산: {total_budget:.1f}분")

scenarios = [
    ("정상 운영", 10.0, 15),   # 15일 경과, 다운타임 10분
    ("장애 다발", 35.0, 15),   # 같은 15일에 다운타임 35분
]

for name, downtime, days in scenarios:
    budget, consumed, burn = budget_status(slo, downtime, days)
    action = "배포 계속 (여유 있음)" if burn < 1.0 else "기능 출시 중단, 신뢰성 작업 우선"
    print(f"[{name}] {days}일 경과, 다운타임 {downtime}분 -> "
          f"소진율 {consumed*100:.1f}%, burn rate {burn:.2f}x -> {action}")

# 용량 계획: 이용률이 1에 가까워질수록 대기시간이 급격히 발산 (M/M/1 근사)
def expected_wait_factor(utilization):
    """대기행렬 이론: 대기시간은 rho / (1 - rho) 에 비례해 발산"""
    if utilization >= 1:
        return float("inf")
    return utilization / (1 - utilization)

print("\n이용률별 대기시간 배율 (M/M/1 근사):")
for rho in [0.5, 0.7, 0.9, 0.95, 0.99]:
    print(f"  rho={rho:.2f} -> wait factor={expected_wait_factor(rho):.2f}")

Exercise

For a service you run, define two SLIs (availability and p99 latency), compute a 30-day SLO and error budget, then plug in the last 30 days of real measurements to work out the budget burn rate.

Practical Connection

For paths mixed with external factors — RPC node dependencies, oracle response latency, settlement transaction confirmation time — pre-defining targets and budgets is what lets you judge where "normal" ends.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

용량 계획·SLO와 에러 예산 TODO

Algorithms · Day 49 / 100 · C. 동시성·성능 엔지니어링 (Day 36–51)

개념

SLO는 사용자 관점의 서비스 수준 지표(SLI)에 대해 정한 목표치이고, 에러 예산은 그 목표가 허용하는 실패의 총량이다. 가용성 목표를 99.9%로 잡았다면 나머지 0.1%가 한 기간 동안 쓸 수 있는 예산이며, 이 예산은 배포 속도와 안정성 사이의 명시적 교환 수단이 된다. 예산이 남으면 더 공격적으로 배포하고, 소진되면 기능 출시를 멈추고 신뢰성 작업에 투입하는 식의 정책으로 운영한다. 용량 계획은 여기에 부하 예측을 결합해, 목표 지연시간을 유지한 채 소화 가능한 최대 부하와 필요한 여유분을 정하는 작업이다. 대기행렬 관점에서 이용률이 1에 가까워질수록 대기시간이 급격히 발산하므로, 평균 이용률이 아니라 피크와 꼬리를 기준으로 여유를 잡아야 한다.

"얼마나 안정적이어야 하는가"를 숫자로 합의해 두지 않으면 장애 대응과 기능 개발의 우선순위 다툼이 매번 감정 싸움이 된다.

코드 · 수식

# 용량 계획·SLO와 에러 예산 — 가용성 목표에서 허용 실패량(에러 예산)을 계산하고,
# 실측 실패율로 예산 소진율을 구해 배포를 계속할지 판단한다.

def error_budget_minutes(slo_percent, period_days=30):
    """기간 동안 허용되는 다운타임(분)"""
    period_minutes = period_days * 24 * 60
    allowed_failure_ratio = 1 - slo_percent / 100
    return period_minutes * allowed_failure_ratio

def budget_status(slo_percent, downtime_minutes_so_far, days_elapsed, period_days=30):
    total_budget = error_budget_minutes(slo_percent, period_days)
    consumed_ratio = downtime_minutes_so_far / total_budget
    # 지금까지 경과한 기간 대비 정상 소진 속도(1.0이면 딱 예산대로 소진 중)
    expected_ratio_by_now = days_elapsed / period_days
    burn_rate = consumed_ratio / expected_ratio_by_now if expected_ratio_by_now else 0
    return total_budget, consumed_ratio, burn_rate

slo = 99.9  # 월 가용성 목표
total_budget = error_budget_minutes(slo)
print(f"SLO {slo}% -> 30일 에러 예산: {total_budget:.1f}분")

scenarios = [
    ("정상 운영", 10.0, 15),   # 15일 경과, 다운타임 10분
    ("장애 다발", 35.0, 15),   # 같은 15일에 다운타임 35분
]

for name, downtime, days in scenarios:
    budget, consumed, burn = budget_status(slo, downtime, days)
    action = "배포 계속 (여유 있음)" if burn < 1.0 else "기능 출시 중단, 신뢰성 작업 우선"
    print(f"[{name}] {days}일 경과, 다운타임 {downtime}분 -> "
          f"소진율 {consumed*100:.1f}%, burn rate {burn:.2f}x -> {action}")

# 용량 계획: 이용률이 1에 가까워질수록 대기시간이 급격히 발산 (M/M/1 근사)
def expected_wait_factor(utilization):
    """대기행렬 이론: 대기시간은 rho / (1 - rho) 에 비례해 발산"""
    if utilization >= 1:
        return float("inf")
    return utilization / (1 - utilization)

print("\n이용률별 대기시간 배율 (M/M/1 근사):")
for rho in [0.5, 0.7, 0.9, 0.95, 0.99]:
    print(f"  rho={rho:.2f} -> wait factor={expected_wait_factor(rho):.2f}")

연습

운영 중인 서비스 하나에 대해 SLI 두 개(가용성·p99 지연)를 정의하고 30일 SLO와 에러 예산을 계산한 뒤, 지난 30일 실측을 대입해 예산 소진율을 구해 보기.

실무 · Verex 연결

RPC 노드 의존, 오라클 응답 지연, 정산 트랜잭션 확정 시간처럼 외부 요인이 섞인 경로일수록 목표와 예산을 미리 정해 두어야 어디까지가 정상인지 판단할 수 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 48. 분산 트레이싱과 샘플링 전략50. 카오스 엔지니어링·장애 주입 설계 →