Language
검색

AI는 왜 Azure 장애 원인을 자꾸 틀리나: 2026년 논문을 실제 500 에러에 적용해봤다

청록색 광섬유 케이블이 패치 패널에 빽빽하게 꽂혀 있는 사진

·

조회수 16
AI에게 클라우드 장애 원인 분석을 맡기면 왜 자꾸 그럴듯한 오답이 나오나?
모델이 약해서가 아니라 절차가 없어서다. 2026년 논문들이 실제 실행 기록을 세어 보니 해석을 지어내고(71.2%), 봐야 할 지표를 건너뛰고(63.9%), 증상을 원인이라고 선언하는(39.9%) 실패가 모델을 바꿔도 그대로 남았다. 처방도 같은 자리에 있다. 가설을 문장이 아니라 목록으로 세우고, 각 가설에 무엇이 나오면 기각인지를 미리 적고, 시계열의 시작 시각부터 맞추는 것이다.

금요일 오후 2시 12분, /api/orders가 500을 뱉기 시작한다. 대시보드는 빨갛고, 슬랙에는 “배포 롤백할까요?”가 올라온다. 요즘은 여기서 한 줄이 더 붙는다. “일단 에이전트한테 물어봤는데 DB 문제래요.”

그 답은 절반만 맞았다. 이 글은 2026년에 나온 클라우드 장애 원인분석(RCA) 논문 세 편이 AI가 어디서 어떻게 틀리는지를 세어 놓은 결과를 가져와, 위 애저 장애 상황에 그대로 적용해 원인까지 내려가는 순서를 만든다. 2026년 트러블슈팅이 달라진 네 가지가 도구 이야기였다면, 이 글은 그 도구를 든 다음에 필요한 절차 이야기다.

1. 문제 상황: 500은 증상이지 원인이 아니다

스택은 흔한 구성이다. Front Door 뒤에 App Service(주문 API), 그 뒤에 Azure SQL 단일 데이터베이스, 인증은 관리 ID(Managed Identity)로 Microsoft Entra ID 토큰을 받아 DB에 붙는다. 관측은 Application Insights와 Log Analytics로 모은다.

관측된 사실은 세 줄뿐이다.

  • 14시 12분부터 /api/orders의 5xx 비율이 2%에서 60%로 올랐다.
  • 같은 시각 Azure SQL의 DTU 사용률이 98%를 찍었다.
  • 마지막 배포는 3일 전이다.

에이전트에게 던지면 대개 “DB 과부하가 원인, 인덱스를 추가하라”가 돌아온다. 실제로 마이크로소프트의 Azure SRE Agent 문서가 드는 예시도 정확히 이 모양이다. 가설 1(배포) 기각, 가설 2(DB 과부하) 검증, 근본 원인은 인덱스 누락. 깔끔하다. 문제는 이번 장애가 그 모양이 아니라는 것이다. DTU 98%는 이 장애에서 원인이 아니라 두 번째 증상이다.

2. 논문이 세어 본 실패: 모델을 바꿔도 안 없어진다

한양대와 오케스트로(OKESTRO) 연구진이 올해 초에 낸 Why Do AI Agents Systematically Fail at Cloud Root Cause Analysis?는 OpenRCA 벤치마크의 장애 335건을 5개 모델(Gemini 2.5 Pro, GPT-5 mini, GPT-OSS 120B, Solar Pro 2, Claude Sonnet 4)로 1,675회 돌리고, 실패한 실행의 추론 기록을 사람이 분류했다. 상위 항목은 이렇다.

실패 패턴 비율 장애 현장에서의 모습
해석 지어내기 71.2% 데이터에 없는 인과를 서사로 메운다
탐색 누락 63.9% 봐야 할 지표·구성요소를 통째로 건너뛴다
증상을 원인이라고 선언 39.9% 표면 이상치에서 조사를 멈춘다
쿼리 코드 오류 27.2% 실행되는 코드가 틀렸는데 결과는 그럴듯하다
텔레메트리 편식 26.9% 한 종류의 데이터만 보고 결론 낸다
시각 어긋남 23.3% 서로 다른 시간창을 겹쳐 놓고 상관관계라고 부른다
교차검증 없음 18.6% 한 번 나온 값을 검증 없이 받아들인다

이 논문의 결론이 중요하다. 이 분포는 다섯 모델에서 거의 그대로 반복됐다. 더 비싼 모델을 붙이는 것으로는 사라지지 않는다는 뜻이다. 같은 연구는 컨트롤러가 실행기에게 자연어 요약만 넘길 때 생기는 지시-코드 불일치(20~26%)를 두고, 생성된 코드와 실행 결과 전문을 함께 넘기자 관련 실패가 최대 15%p 줄고 실행 시간도 22.3% 짧아졌다고 보고한다. 고칠 곳이 프롬프트가 아니라 구조라는 이야기다.

같은 방향의 결과가 하나 더 있다. 1월에 나온 Stalled, Biased, and Confused는 여섯 개 모델을 ReAct와 Plan-and-Execute 두 워크플로에 얹어 4만 8천 건의 장애 시나리오를 228일치 실행으로 돌리고, RCA 추론 실패 16종의 분류표를 만들었다. 제목이 곧 요약이다. 멈추고(stalled), 처음 세운 가설에 편향되고(biased), 신호를 혼동한다(confused).

반대로 무엇이 통했는지도 나와 있다. 2025년 12월의 PRAXIS는 LLM을 자유 탐색하는 조사관으로 두지 않고 그래프 위를 이동하는 정책으로 썼다. 서비스 의존성 그래프와 코드 의존성 그래프를 깔아 두고 그 위만 걷게 하자, ReAct 기준선 대비 정확도가 최대 6.3배 오르고 토큰 사용량은 5.3분의 1로 줄었다. 탐색 범위를 좁힌 쪽이 이겼다.

3. 논문의 처방을 조사 절차로 옮기면

세 편에서 공통으로 뽑히는 규칙은 네 개다. 사람이 조사하든 에이전트에게 시키든 똑같이 적용된다.

  1. 시작 시각부터 못 박는다. 상관관계를 보기 전에 각 신호가 언제 처음 틀어졌는지를 1분 단위로 확정한다. 시각 어긋남(23.3%)이 여기서 걸러진다.
  2. 가설을 목록으로 세우고, 기각 조건을 먼저 쓴다. “무엇이 나오면 이 가설을 버리는가”를 쿼리 옆에 적어 둔다. 편향과 증상-원인 혼동을 막는 가장 싼 장치다.
  3. 한 신호로 결론 내지 않는다. 메트릭·로그·트레이스·변경 이력 중 최소 두 종류가 같은 방향을 가리킬 때만 넘어간다.
  4. 원인 후보가 나오면 한 홉 더 내려간다. “그것은 왜 그렇게 됐나”에 답이 안 나오면, 아직 증상을 보고 있는 것이다.

4. 실제 적용: 네 개의 가설과 기각 조건

앞의 장애에 그대로 넣는다. 각 가설에 쿼리와 기각 조건이 붙는다.

가설 1 — 최근 배포·설정 변경이 깼다. 기각 조건: 오류 시작 시각 이전 6시간 안에 쓰기 작업이 없다.

AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue has_any (
    "MICROSOFT.WEB/SITES/WRITE",
    "MICROSOFT.WEB/SITES/CONFIG/WRITE",
    "MICROSOFT.SQL/SERVERS/DATABASES/WRITE")
| project TimeGenerated, Caller, OperationNameValue, ActivityStatusValue, _ResourceId
| order by TimeGenerated desc

결과는 3일 전이 마지막이었다. 기각. 배포 롤백은 이 시점에서 후보에서 빠진다.

가설 2 — 상위 의존성(플랫폼) 장애다. 기각 조건: 해당 시간대에 리소스 상태·서비스 상태 이벤트가 없다.

az monitor activity-log list \
  --resource-group rg-orders \
  --start-time 2026-09-04T04:30Z \
  --query "[?category.value=='ResourceHealth' || category.value=='ServiceHealth'].{t:eventTimestamp, cat:category.value, status:properties.currentHealthStatus}" \
  -o table

비어 있었다. 기각. 다만 플랫폼 쪽 공지는 늦게 뜨는 일이 잦으니, 조사 종료 전에 한 번 더 본다.

가설 3 — DB가 포화됐다. 기각 조건: 오류 시작 시각에 DTU·세션이 평소 범위다.

AzureMetrics
| where TimeGenerated between (ago(6h) .. now())
| where ResourceProvider == "MICROSOFT.SQL"
| where MetricName in ("dtu_consumption_percent", "sessions_percent", "connection_failed")
| summarize avg_v = avg(Average), max_v = max(Maximum)
    by MetricName, bin(TimeGenerated, 5m)
| order by TimeGenerated asc

DTU 98%, 세션 사용률 95%, 연결 실패 급증. 검증됨. 여기서 멈추면 논문이 센 39.9%짜리 실패가 된다. 규칙 4를 적용한다. DTU는 왜 올랐나.

가설 4 — 애플리케이션이 DB를 평소보다 훨씬 많이 두드리고 있다. 기각 조건: 요청 수 대비 DB 호출 수 비율이 평소와 같다.

// App Insights: 요청 1건당 DB 호출이 몇 번인가
let win = 6h;
let reqs = requests
  | where timestamp > ago(win)
  | summarize req = count() by bin(timestamp, 5m);
dependencies
| where timestamp > ago(win)
| where type has "SQL"
| summarize dep = count(), fails = countif(success == false),
            p95 = percentile(duration, 95) by bin(timestamp, 5m)
| join kind=inner reqs on timestamp
| extend calls_per_request = round(todouble(dep) / req, 2)
| project timestamp, req, dep, calls_per_request, fails, p95
| order by timestamp asc

요청 수는 그대로인데 DB 호출이 요청당 3.1회에서 12.4회로 뛰었다. 재시도가 돌고 있다는 뜻이다. 무엇에 대한 재시도인가. 연결 획득 실패다. 관리 ID로 토큰을 받아 DB에 붙는 구조이므로 한 홉 더 내려간다.

// 관리 ID 로그인은 SigninLogs 가 아니라 이 테이블이다
// (Entra ID 진단 설정에서 ManagedIdentitySignInLogs 를 보내야 쌓인다)
AADManagedIdentitySignInLogs
| where TimeGenerated between (ago(6h) .. now())
| where ServicePrincipalName has "orders-api"
| summarize total = count(), failed = countif(ResultType != 0)
    by bin(TimeGenerated, 5m)
| order by TimeGenerated asc

14시 10분부터 서비스 주체 토큰 요청이 분당 40건에서 900건으로 뛰었고, 그중 상당수가 스로틀링으로 실패했다.

정리된 인과 사슬은 이렇다. 토큰 캐시가 만료되는 순간 요청마다 새 자격증명 객체를 만드는 코드 경로가 깨어난다 → 토큰 발급이 스로틀링된다 → 연결 획득이 실패하고 재시도가 폭주한다 → 커넥션 풀과 세션이 고갈되며 DTU가 98%까지 오른다 → 앞단이 500을 뱉는다. 즉 원인은 증상에서 두 홉 떨어져 있었다. 인덱스를 추가했다면 아무 일도 안 일어났을 것이고, DB를 한 단계 올렸다면 장애 시간만 늘렸을 것이다.

조치는 두 단계로 갈린다. 완화는 재시도에 지수 백오프와 상한을 걸고 커넥션 풀 상한을 낮추는 것이다. 근본 조치는 자격증명 객체(DefaultAzureCredential 등)를 요청마다 만들지 않고 재사용해 토큰 캐시를 살리는 것이다.

5. 그래서 에이전트에게 무엇을 맡기나

논문들이 가리키는 결론은 “쓰지 말라”가 아니라 “탐색 범위를 사람이 정해 주라”다. PRAXIS가 그래프로 한 일을 우리는 절차로 한다.

  • 맡길 것: 시각 정렬, 변경 이력 수집, 여러 테이블을 가로지르는 쿼리 초안, 타임라인과 보고서 초안.
  • 뺏을 것: 최종 인과 판정. 조사 종료 선언은 사람이 한다.
  • 요구할 것: 결론마다 근거 쿼리와 그 실행 결과를 함께 내놓게 한다. 근거가 첨부되지 않은 문장은 그 자체로 71.2%짜리 실패 후보다.
  • 금지할 것: 프로덕션 쓰기 권한. 읽기 전용으로 붙이고, 조치는 사람이 실행한다.

Azure SRE Agent를 쓴다면 소스 제어 연결과 지식 문서 업로드를 먼저 하는 편이 낫다. 에이전트가 코드와 과거 인시던트를 못 보면, 결국 우리가 위에서 본 것과 같은 표면 지표만 놓고 추론한다.

6. 이번 주에 할 수 있는 것

  • 가설-기각 조건 템플릿을 인시던트 채널에 고정한다. 가설 한 줄, 쿼리 한 개, 기각 조건 한 줄.
  • 자주 쓰는 KQL 네 개(활동 로그·상태 이벤트·리소스 메트릭·요청당 의존성 호출)를 저장 쿼리로 만들어 둔다.
  • “요청당 DB 호출 수”를 대시보드에 상시로 띄운다. 재시도 폭주를 가장 빨리 드러내는 지표다.
  • 토큰·자격증명 객체를 요청마다 새로 만드는 코드 경로가 있는지 한 번 훑는다.
  • 조사 회고에 “몇 홉에서 멈췄나”를 항목으로 넣는다.

⚠️ 이 글의 장애는 실제 고객 사고가 아니라, 공개된 애저 문서와 위 논문들의 실패 패턴을 조합해 재구성한 시나리오다. KQL과 CLI 예시는 테이블·메트릭 이름이 진단 설정과 SDK 버전에 따라 달라질 수 있으므로, 자사 워크스페이스 스키마를 먼저 확인하고 쓰는 것이 좋다. 논문 수치는 각 벤치마크(OpenRCA 등) 안에서의 값이며, 운영 환경의 성적을 그대로 보장하지 않는다.

자주 묻는 질문

AI 에이전트가 낸 원인 분석을 어디까지 믿어야 하나요?

근거 쿼리와 그 실행 결과가 함께 붙어 있는 부분까지만 믿는 편이 안전합니다. 2026년 연구에서 가장 많이 관찰된 실패가 데이터에 없는 인과를 서사로 메우는 것(71.2%)이었고, 이 실패는 모델 등급을 올려도 거의 줄지 않았습니다. 결론 문장이 아니라 증거 사슬을 검토 대상으로 두시는 것이 좋습니다.

DTU가 98%면 그냥 데이터베이스를 올리면 되지 않나요?

포화가 원인일 때만 맞는 조치입니다. 이 글의 사례처럼 재시도 폭주가 포화를 만든 경우에는, 스케일업이 장애 시간을 늘리고 비용만 남깁니다. 원인 후보가 나오면 “그것은 왜 그렇게 됐나”에 한 번 더 답해 보시고, 답이 안 나오면 아직 증상 단계라고 보시면 됩니다.

관측 데이터가 부실한데도 이 절차가 의미가 있나요?

있습니다. 다만 순서가 있습니다. 활동 로그와 리소스 메트릭은 진단 설정만 켜면 바로 쌓이므로 가설 1~3은 오늘도 검증할 수 있습니다. 가설 4처럼 요청당 의존성 호출을 보려면 Application Insights 계측이 필요하니, 그 부분만 우선순위를 올려 두시면 됩니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *