SLI는 사용자가 경험한 것의 측정값이고, SLO는 그 측정값에 대해 세우는 목표이며, SLA는 목표를 놓쳤을 때 무슨 일이 일어나는지 정한 계약이고, 에러 버짓(error budget)은 100퍼센트와 SLO 사이의 차이를 허용된 실패 요청 수로 센 것이다. 1천만 건의 요청을 처리하는 서비스에서 30일 동안 99.9퍼센트 SLO는 실패 1만 건의 예산이다. 이 네 숫자가 조직의 나머지가 답하지 못하는 질문에 SRE가 답하는 방법이다. 이 서비스는 얼마나 신뢰할 수 있어야 하고, 오늘 출시해도 안전한가?

이 글은 Budget for Failure 시리즈의 2부다. 1부는 SRE 역할을 설명했고 그 역할의 의존성으로 끝났다. 숫자 없이는 아무것도 작동하지 않는다. 이 부는 여전히 101 수준이다. 용어를 정의하고, AWS SLA가 실제로 무엇을 약속하는지 읽고, 이미 갖고 있는 로드 밸런서 메트릭으로 첫 SLI를 계산하고, 그 결과를 정책이 붙은 예산으로 바꾼다.

SLI: 유효한 이벤트 대비 좋은 이벤트의 비율

서비스 수준 지표(service level indicator)는 0과 100퍼센트 사이의 숫자이고, 매번 같은 방식으로 계산한다. 좋은 이벤트를 유효한 이벤트로 나눈다. 요청 기반 서비스에서 가장 유용한 두 SLI는 다음과 같다.

  • 가용성: 서버 에러 없이 응답한 요청을 전체 요청으로 나눈 값.
  • 지연: 임계값(예: 300ms)보다 빠르게 응답한 요청을 전체 요청으로 나눈 값.

이 정의에는 두 가지 설계 결정이 숨어 있다. 첫째, 무엇을 유효로 칠 것인가. 존재하지 않는 URL에 대한 404는 서비스가 올바르게 동작한 것이고, 자체 레이트 리미터가 낸 429는 논쟁의 여지가 있으며, 500은 아니다. 한 번 결정하고 적어 두라. 분모가 SLI가 조용히 조작되는 자리이기 때문이다. 둘째, 어디서 측정할 것인가. 사용자에 가까울수록 숫자는 정직하다. 로드 밸런서에서 잡은 메트릭은 연결을 거부한 타깃처럼 애플리케이션이 결코 로그에 남기지 않는 실패를 본다. 애플리케이션 내부에서 잡은 메트릭은 그것을 놓친다.

평균은 SLI의 자격이 없다. 평균 지연 120ms는 스무 건 중 한 건이 4초 걸리는 상황과 양립한다. 비율 형태("요청의 97.2퍼센트가 300ms 이내")는 얼마나 많은 사용자가 나쁜 경험을 했는지 말해주고, 그것이 당신이 제한하려는 대상이다.

SLO: 목표, 그리고 그것을 측정하는 기간

서비스 수준 목표(service level objective)는 SLI에 목표와 시간 창을 더한 것이다. "요청의 99.9퍼센트가 성공한다, 롤링 30일 기준으로 측정." 세 부분 모두 필수다. 기간 없는 목표는 반증할 수 없다. 사후에 더 긴 기간을 고르면 어떤 나쁜 한 시간도 평균 속에 묻어버릴 수 있기 때문이다.

목표는 나인(9)이 등장하는 자리이고, 나인은 허용 다운타임으로 바꿔 생각하면 추론하기 쉽다. 30일 창(43,200분) 기준으로:

SLO30일 중 허용되는 불량 시간1천만 건당 허용되는 불량 요청
99%7시간 12분100,000
99.5%3시간 36분50,000
99.9%43분 12초10,000
99.95%21분 36초5,000
99.99%4분 19초1,000
99.999%26초100

표를 아래에서부터 읽어라. 99.99퍼센트에서는 한 달에 약 4분이 있다. 새벽 3시에 호출받은 사람이 노트북을 찾는 데만 그보다 오래 걸리므로, 포 나인 SLO는 자동 감지와 자동 복구에 대한 약속이고 사람은 그 뒤에야 관여한다. 나인이 하나 늘 때마다 허용 실패는 대략 10분의 1로 줄고, 실무에서는 비용이 크게 뛴다. 올바른 SLO는 슬라이드에서 멋져 보이는 가장 높은 숫자가 아니라, 그 위로는 사용자가 차이를 느끼지 못할 가장 낮은 숫자다.

100퍼센트는 결코 올바른 목표가 아니다. 모든 배포, 모든 마이그레이션, 당신이 통제하지 못하는 모든 의존성을 금지하는 셈이고, 사용자는 당신이 운영하는 그 무엇보다 훨씬 덜 신뢰할 수 있는 가정용 Wi-Fi와 모바일 네트워크를 거쳐 당신에게 닿는다.

SLA: 계약, 그리고 AWS가 실제로 빚지는 것

서비스 수준 협약(service level agreement)은 결과가 붙은 SLO이며 고객과 서명한 것이다. 결과는 보통 돈이다. 그것이 차이의 전부이고, SLA가 항상 내부 SLO보다 느슨한 이유다. 위반이 환불이 되기 훨씬 전에 알아채고 대응하고 싶기 때문이다.

AWS는 우리 대부분이 가장 먼저 읽는 SLA이고, 그 문구가 개념을 가르쳐 주기 때문에 꼼꼼히 읽을 가치가 있다. 전형적인 웹 서비스 아래에 놓이는 네 가지:

서비스와 조건월간 가동률 약속미달 시 크레딧
EC2, 두 개 이상 AZ에 걸친 인스턴스(Region-Level)99.99%10%, 99.0% 미만이면 30%, 95.0% 미만이면 100%
EC2, 단일 인스턴스(Instance-Level)99.5%10%, 99.0% 미만이면 30%, 95.0% 미만이면 100%
Elastic Load Balancing, 두 개 이상 AZ에 걸친 로드 밸런서99.99%10%, 99.0% 미만이면 30%, 95.0% 미만이면 100%
RDS, Multi-AZ DB 인스턴스 또는 클러스터99.95%10%, 99.0% 미만이면 25%, 95.0% 미만이면 100%
S3 Standard99.9%10%, 99.0% 미만이면 25%, 95.0% 미만이면 100%

그 문서들에서 퍼센트보다 중요한 것이 세 가지 있다.

구제 수단은 서비스 크레딧이다. S3 Standard가 한 달 동안 99.5퍼센트로 떨어지면, 증거를 첨부해 청구한 뒤, 해당 리전의 그달 S3 요금의 10퍼센트를 향후 청구서에서 차감받을 자격이 생긴다. 당신의 잃어버린 매출, 당신 자신의 SLA 위약금, 당신의 주말은 계산에 들어가지 않는다. SLA는 가격 조건이다. 보험이 아니고, 서비스가 어떻게 동작할지에 대한 예측도 아니다.

약속은 당신의 아키텍처를 조건으로 한다. EC2의 99.99퍼센트는 실행 중인 모든 인스턴스가 리전 내 두 개 이상의 가용 영역에 배포되어 있을 때만 적용된다. 한 존에서 돌리면 99.5퍼센트의 인스턴스 수준 약속을 받고, 그것은 한 달에 약 3.6시간의 다운타임을 허용한다. SLA는 AWS가 당신에게 기대하는 설계를 담고 있다.

"사용 불가"에는 좁은 법적 정의가 있다. 각 SLA는 분 단위 또는 5분 간격 단위로 자체 측정 방식을 정의하고, 자체 예외 조항을 둔다. 느린 것은 사용 불가가 아니다. 당신이 의존하는 어느 한 API의 에러율 상승은 아예 집계되지 않을 수도 있다.

이미 갖고 있는 메트릭으로 만드는 첫 SLI

서비스가 Application Load Balancer 뒤에 있다면 CloudWatch는 이미 가용성 SLI를 갖고 있다. AWS/ApplicationELB 네임스페이스의 메트릭 세 개면 충분하다. RequestCount, HTTPCode_Target_5XX_Count(당신의 코드가 반환한 에러), 그리고 HTTPCode_ELB_5XX_Count(예를 들어 건강한 타깃이 하나도 응답하지 않았을 때 로드 밸런서 자체가 생성한 에러). 이것을 sli.json으로 저장하라.

[
  { "Id": "req", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "RequestCount",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "t5xx", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_Target_5XX_Count",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "e5xx", "ReturnData": false,
    "MetricStat": { "Stat": "Sum", "Period": 86400,
      "Metric": { "Namespace": "AWS/ApplicationELB", "MetricName": "HTTPCode_ELB_5XX_Count",
        "Dimensions": [{ "Name": "LoadBalancer", "Value": "app/checkout/0123456789abcdef" }] } } },
  { "Id": "sli", "Label": "availability_percent",
    "Expression": "100 * (req - FILL(t5xx, 0) - FILL(e5xx, 0)) / req" }
]
aws cloudwatch get-metric-data \
  --start-time "$(date -u -v-30d +%Y-%m-%dT00:00:00Z)" \
  --end-time "$(date -u +%Y-%m-%dT00:00:00Z)" \
  --metric-data-queries file://sli.json \
  --query 'MetricDataResults[0].[Timestamps,Values]'

이것은 지난 30일 동안 하루에 하나씩 가용성 수치를 반환한다(date -v 구문은 macOS용이고, Linux에서는 date -u -d '30 days ago'를 쓴다). FILL(…, 0)이 중요하다. CloudWatch는 에러가 0건인 기간에는 에러 메트릭의 데이터포인트를 아예 발행하지 않으므로, fill이 없으면 완벽한 하루가 100퍼센트 대신 누락 데이터로 돌아온다.

지연의 경우 같은 네임스페이스에 TargetResponseTime이 있고, CloudWatch의 백분위 순위 통계가 이것을 비율 형태로 바로 바꿔 준다. PR(:0.3)은 300ms 이내에 완료된 요청의 비율을 반환한다.

에러 버짓: SLO가 쓰도록 허락한 것

비즈니스가 99.9퍼센트면 충분하다고 합의하는 순간, 나머지 0.1퍼센트는 없애야 할 결함이기를 멈추고 쓸 수 있는 허용량이 된다. 기간이 열흘 남은 시점에 서비스가 99.97퍼센트에 있다면 위험을 감수할 여지가 있다. 마이그레이션을 내보내고, 클러스터를 업그레이드하고, 페일오버 테스트를 돌려라. 99.88퍼센트에 있다면 여지가 없고, 그것을 확정하기 위해 누구도 논쟁에서 이길 필요가 없다. 매 기간을 예산에 손도 대지 않은 채 끝내는 서비스 역시 승리가 아니다. SLO가 사용자에게 필요한 것보다 느슨하거나, 팀이 아무도 요구하지 않은 신뢰성에 돈을 내고 있는 것이다.

예산은 이벤트로 셀 수도 시간으로 셀 수도 있고, 둘은 다르게 움직인다.

SLO                      99.9% over 30 days
requests in window       10,000,000
error budget             (1 - 0.999) x 10,000,000 = 10,000 failed requests

bad requests so far      6,400
budget consumed          6,400 / 10,000 = 64%
budget remaining         3,600 requests = 36%

이벤트 기반 예산은 실패에 트래픽의 가중치를 준다. 화요일 피크 시간의 10분 전면 장애는 일요일 새벽 4시의 10분보다 훨씬 많은 비용이 들고, 이것은 사용자가 경험한 것과 일치한다. 요청 기반인 모든 것에 선호할 형태다.

시간 기반 예산은 불량 분(minute)을 센다. 30일 동안 99.9퍼센트는 43분 12초다. 비엔지니어에게 설명하기 쉽고, 배치 파이프라인이나 트래픽이 적은 내부 도구처럼 꾸준한 요청 흐름이 없는 서비스에 맞는다. 대가는 한 분이 완전히 나쁘거나 완전히 좋은 것으로만 집계된다는 점이다. 그래서 무엇이 한 분을 나쁘게 만드는지에 대한 규칙(예: "에러율 1퍼센트 초과")이 필요하고, 부분 장애는 보이지 않거나 전부로 집계된다.

기간은 목표만큼 중요하다. 롤링 30일 창은 항상 최근 과거를 반영하고, 나쁜 하루는 정확히 30일 뒤에 계산에서 빠진다. 사용자 경험을 추적하기에 더 나은 선택이다. 달력 창은 매달 1일에 초기화되어 보고 주기와 대부분의 SLA에 맞아떨어지지만, 한 가지 나쁜 속성이 있다. 2일에 난 사건은 4주 동안 예산을 쓰고, 30일에 난 같은 사건은 이틀 뒤에 용서된다.

무엇이 예산을 쓰는가

사용자의 요청을 실패하게 만드는 모든 것은 누가 일으켰든 같은 계좌에서 빠져나간다. 그게 핵심이다. 전형적인 팀이 쓰는 양이 큰 순서로 대략 나열하면:

  • 변경. 배포, 설정 푸시, 피처 플래그, 스키마 마이그레이션. 대부분의 포스트모템 모음에서 변경은 사건의 가장 큰 단일 원인이다. DORA의 변경 실패율을 추적하고 있다면 이미 가장 큰 소비자를 측정하고 있는 것이다.
  • 의존성. 리전 API 둔화, 서드파티 결제 공급자, DNS 문제. 당신 잘못이 아니어도 당신의 예산이다. 사용자는 누구 잘못인지 신경 쓰지 않기 때문이다.
  • 용량과 부하. 스로틀링, 식은 캐시, 2분 늦게 반응하는 스케일링 정책.
  • 계획된 작업. 유지보수 창, 페일오버 테스트, 게임 데이. 실패 요청 300건이 드는 의도적 존 대피 훈련은 잘 쓴 예산이고, 예산이 있다는 사실이 그 훈련을 승인 가능하게 만든다.
  • 하드웨어와 플랫폼. 인스턴스 퇴역, 존 이벤트. 아키텍처가 멀티 AZ라면 사람들의 예상보다 작다.

장부를 유지하라. 각 사건이나 위험한 변경 뒤에 그것이 예산을 얼마나 소비했고 원인이 무엇이었는지 기록하라. 석 달치 장부는 어떤 로드맵 논의보다 신뢰성 엔지니어링 시간이 어디로 가야 하는지 잘 말해준다.

날짜이벤트실패 요청예산 비중
8월 3일잘못된 연결 풀 설정으로 배포, 9분 만에 롤백3,10031%
8월 11일결제 공급자 지연, 체크아웃에서 타임아웃2,20022%
8월 14일계획된 RDS 페일오버 테스트3503.5%
기간 전체배경 에러, 단일 원인 없음7507.5%

이 예에서 배포 한 번이 서드파티 장애와 계획된 테스트를 합친 것보다 많은 비용을 냈다. 투자 근거는 저절로 쓰인다. 다른 무엇보다 먼저 카나리 분석과 자동 롤백.

정책이 곧 제품이다

결과가 없는 예산은 대시보드다. 이것을 작동하게 만드는 것은 엔지니어링과 제품 리더십이 사전에 합의한, 예산이 바닥날 때 무엇이 바뀌는지 적은 짧은 서면 정책이다. Google SRE Workbook은 구조를 베낄 만한 예시 정책을 공개하고 있다. 쓸 만한 첫 버전은 반 페이지에 들어간다.

  • 예산 50퍼센트 초과: 정상 릴리스 프로세스. 위험한 변경과 실험을 권장한다.
  • 예산 50퍼센트 미만: 변경에는 카나리 단계와 검증된 롤백이 필요하다. 프로덕션에서 실험 금지.
  • 예산 소진: 서비스가 SLO 안으로 돌아올 때까지 기능 릴리스를 멈춘다. 신뢰성 수정, 보안 패치, 예산 복구에 필요한 변경만 나간다. 팀의 우선순위는 최근 포스트모템의 상위 항목이다.
  • 단일 사건이 예산의 20퍼센트 이상을 소비: 포스트모템이 필수이며, 최소 하나의 높은 우선순위 조치 항목이 있어야 한다.
  • 의견 불일치: 지명된 임원이 결정한다. 에스컬레이션 경로는 필요해지기 전에 적어 둔다.

두 가지 속성이 이것을 지탱한다. 대칭적이다. 예산이 바닥났을 때 릴리스를 동결하는 바로 그 문서가, 예산이 건강할 때는 운영에게 릴리스를 막지 말라고 말한다. 그리고 사전에 합의됐다. 제품 책임자가 평온한 주에 서명했으므로, 나쁜 주에 그것을 발동하는 것은 대결이 아니라 집행이다. 리더십이 기능 작업을 멈출 수 있는 정책에 서명하지 않는다면 아직 SLO가 없는 것이다. 메트릭이 있을 뿐이다.

CloudWatch에게 장부를 맡기기

위의 SLI는 get-metric-data로 손수 계산했다. 진행 중인 예산을 위해 CloudWatch에는 Application Signals 안에 관리형 SLO 기능이 있다. 지표, 달성 목표, 간격을 정의하면 달성률과 남은 에러 버짓을 지속적으로 추적한다. 그 모델의 세 가지 선택이 이 글에 그대로 대응된다.

  • 기간 기반 또는 요청 기반. 기간 기반 SLO는 간격을 기간(예: 1분)으로 나누고, 각각을 좋음 또는 나쁨으로 판정하며, 전체 기간 대비 좋은 기간을 보고한다. 요청 기반 SLO는 전체 요청 대비 좋은 요청을 센다. 위에서 설명한 시간 기반 예산과 이벤트 기반 예산이다.
  • 롤링 또는 달력 간격. 둘 다 지원되며, 트레이드오프는 이미 설명했다.
  • SLI를 무엇 위에 세우는가. Application Signals로 계측된 서비스는 표준 지연 및 가용성 메트릭을 얻고, 5xx 응답은 결함으로, 4xx 응답은 성공으로 집계된다. 계측되어 있지 않다면 단일 시계열을 내는 어떤 CloudWatch 메트릭이나 metric math 식에도 SLO를 걸 수 있고, 여기에는 위의 로드 밸런서 식이 포함된다.

콘솔은 결과를 두 수치로 보여준다. 목표 대비 달성률, 그리고 남은 예산. 요청 기반 SLO에서는 간격 중에 남은 예산이 줄 뿐 아니라 늘 수도 있다. 지금까지의 전체 요청 대비 좋은 요청의 비율에 달려 있어서, 깨끗한 트래픽의 조용한 한 주가 앞선 사건을 희석하기 때문이다.

실패 모드와 주시할 것

퍼센트를 평균 내기. 요청 100건에 90퍼센트인 하루와 요청 100만 건에 99.99퍼센트인 하루의 평균은 95가 아니다. 30일 SLI는 전체 유효 요청 대비 전체 좋은 요청이다. 분자끼리 분모끼리 더한 다음 나눠라.

구멍 난 분모. RequestCount는 로드 밸런서가 타깃을 고를 수 있었던 요청만 세므로, 위 식은 전면 장애에서 피해를 약간 과대평가한다. 틀리더라도 안전한 방향이다. 이 SLI는 로드 밸런서에 아예 닿지 못한 요청도 볼 수 없다. DNS 실패, WAF 차단, 망가진 네트워크 위의 클라이언트.

아무도 합의하지 않은 SLO. 플랫폼 팀이 혼자 고른 목표는 출시 일정과 충돌할 때 아무 권위가 없다. 숫자는 로드맵을 소유한 사람이 소유해야 한다.

너무 많은 SLO. 사용자 대면 서비스당 하나나 둘로 시작하라. 돈을 버는 요청 경로의 가용성과 지연. SLO 스무 개는 예산 스무 개를 만들고, 그중 하나는 항상 소진 상태이며, 정책은 소음이 된다.

데이터를 보기 전에 목표를 정하기. 먼저 지난 30일에 대해 쿼리를 돌려라. SLI를 보기 전에 고른 SLO는 소망이다. 측정된 이력보다 약간 낮게 잡고 의도적으로 조여 가는 것이 방어할 수 있는 목표다.

월말에 알게 되기. 월간 보고서에서 읽은 예산은 과거를 설명할 수 있을 뿐이다. 예산이 남아 있는 동안 행동하려면 소비 속도에 대한 알림이 필요하고, 서비스가 덜 쓰게 만들려면 그 코드의 변경이 필요하다. 둘 다 3부다.

다음으로 읽을 글

AWS, 클라우드 아키텍처, SRE 실무, CI/CD 파이프라인, Linux 등에 대한 컨설팅은 ercanermis.com에서 시작하라.

참고 자료