장애 타임라인 집계
100분 안팎
학습 목표
표준 입력의 시간·대상·상태 로그를 정렬해 최초 실패와 복구 시점을 출력합니다.
개념
알림 도착 순서는 사건 순서가 아닙니다
여러 점검기가 로그를 보낼 때 수집 지연으로 복구 행이 실패 행보다 먼저 들어올 수 있습니다. 입력 순서대로 첫 FAIL을 찾으면 최초 실패 관측이 달라지고 대응 시간을 잘못 설명하게 됩니다. 이번 레슨은 표준 입력의 시간·대상·상태를 해석하여 대상별 최초 실패와 그 뒤 첫 정상 관측을 출력합니다. 이 요약은 자료에 기록된 시점이며 실제 사용자 장애 시작과 종료를 확정하는 측정은 아닙니다.
입력 계약을 작게 정합니다
한 줄은 YYYY-MM-DDTHH:MM:SSZ 대상 OK 또는 FAIL의 세 필드입니다. Z는 UTC를 뜻하며 초 단위까지 씁니다. 대상에는 공백이 없고 출력은 대상 이름의 사전식 순서입니다. 공백 줄은 무시합니다. 비어 있는 입력이면 사건 행은 없고 INVALID 0만 출력합니다. 임의의 시간대나 소수 초는 이번 입력에서 받지 않습니다. 형식을 좁히는 대신 다른 형식의 행은 잘못된 자료 수로 드러냅니다.
형식 검사와 날짜 검사를 나눕니다
정규식으로 필드와 고정 시각 모양을 확인한 다음 datetime.strptime로 실제 날짜를 검사합니다. 2026-02-30처럼 모양은 맞지만 존재하지 않는 날짜는 제외하고 INVALID를 증가시킵니다. 상태가 WARN이면 이 문제의 계약 밖이므로 같은 방식으로 처리합니다. 잘못된 자료를 정상 행으로 고쳐 해석하지 않습니다. 분석에서 제외한 행 수가 보이면 요약의 신뢰도를 검토할 수 있습니다.
시간 비교는 같은 기준에서 수행합니다
모든 행이 고정 UTC 형식이면 시각 문자열의 사전식 순서와 시간 순서가 일치합니다. 다만 이 성질은 형식 검사를 통과한 문자열에 한정합니다. 다른 시간대가 섞인 자료는 날짜 객체와 시간대 변환 정책을 추가해야 합니다. 정렬 전에 검증하는 이유는 잘못된 날짜가 순서와 최초 사건을 오염시키지 않게 하기 위해서입니다. 원래 입력 행 번호도 보존하여 같은 초 안의 순서를 정합니다.
같은 시각에서는 원래 순서를 유지합니다
점검 주기나 로그 해상도 때문에 FAIL과 OK가 같은 초에 나타날 수 있습니다. 이 문제는 입력 행 번호를 두 번째 정렬 키로 사용합니다. 같은 초의 OK가 FAIL보다 앞이면 그 OK는 해당 실패의 복구로 쓰지 않습니다. 뒤라면 복구 시점이 같은 초가 될 수 있습니다. 실제 분산 로그에서는 수집 순서가 인과 관계를 보장하지 않으므로 이 결과를 분석 계약에 따른 선택이라고 설명합니다.
대상별로 분리하여 집계합니다
app의 실패를 disk의 정상 행으로 닫으면 아직 복구되지 않은 앱을 정상으로 보고하게 됩니다. 유효한 행을 대상별로 묶고 각 그룹 안에서 정렬합니다. 각 대상의 첫 FAIL 위치를 찾은 뒤 그 위치 이후 처음 만나는 OK를 복구 관측으로 선택합니다. FAIL이 전혀 없으면 최초 실패와 복구 모두 NONE입니다. 처음 OK가 있었더라도 뒤에 실패한 경우는 그 앞 OK를 복구로 재사용하지 않습니다.
반복 실패는 최초 사건에 포함합니다
FAIL이 연속되면 이번 요약에서는 첫 FAIL을 유지하고 뒤의 첫 OK만 찾습니다. OK 이후 다시 FAIL이 있더라도 이 문제는 첫 사건의 요약만 출력합니다. 마지막 상태나 모든 장애 구간을 요구하는 문제와 다릅니다. 실제 인계에서 재발을 숨기지 않으려면 전체 정렬 로그를 함께 연결하거나 구간별 집계기를 확장합니다. 요약의 목적과 버리는 정보가 무엇인지 문서에 적습니다.
복구가 없으면 NONE으로 남깁니다
마지막 유효 행까지 OK를 찾지 못하면 복구 칸에 NONE을 출력합니다. 이것은 관측 기간 안에 복구 확인이 없다는 뜻입니다. 이후에 실제 복구되지 않았다고 단정하거나 현재 시각을 복구 시각으로 넣지 않습니다. 자료가 잘렸거나 수집기가 중단됐을 가능성도 있습니다. 다음 담당자에게 마지막 관측 시각과 추가 수집 필요성을 전달하고 완료 상태를 보류합니다.
출력은 사람이 비교하기 쉽게 고정합니다
각 대상은 대상 이름, 최초 실패 시각 또는 NONE, 복구 시각 또는 NONE의 세 필드를 한 줄에 출력합니다. 마지막 행은 INVALID와 잘못된 비공백 행 수입니다. 로그 외 설명이나 디버그 print를 표준 출력에 섞지 않습니다. 브라우저 채점기는 기대 문자열을 비교하므로 대상 정렬과 줄바꿈도 계약의 일부입니다. 정상 대상만 있는 입력도 출력 행을 유지하여 검사 대상 자체가 빠지지 않게 합니다.
구현은 파싱·묶기·요약으로 나눕니다
첫 루프는 입력을 검증하고 events에 대상별 (시각, 행 번호, 상태)를 추가합니다. 다음 루프는 대상 이름을 정렬하고 events[target]을 정렬합니다. first와 recovered는 NONE으로 시작합니다. 첫 FAIL을 만날 때 first를 채우고 그 이후 OK를 만날 때 recovered를 채운 뒤 중단합니다. 초기화 위치가 대상 루프 밖이면 이전 대상의 실패가 다음 대상에 섞이므로 각 그룹마다 새 변수로 시작합니다.
확인 예제의 판단 경로
입력이 app의 09:03 OK, 09:01 FAIL, 09:00 OK 순서라면 정렬 뒤 09:00 OK, 09:01 FAIL, 09:03 OK가 됩니다. 최초 실패는 09:01, 복구 관측은 09:03입니다. 09:00 정상 행은 실패 전 상태를 보여 주지만 복구는 아닙니다. 이 두 관측 사이의 시간을 실제 장애 지속 시간과 같다고 쓰지 않습니다. 점검 주기 사이에서 실제 장애가 언제 시작됐는지는 이 입력으로 알 수 없습니다.
형식 오류를 세는 이유
시각 대신 ERROR가 있는 행을 조용히 버리면 누락된 실패가 없었던 일처럼 보입니다. INVALID가 양수이면 원본 행을 찾아 수집기 형식이 달랐는지, 자료가 잘렸는지 검토합니다. 이 실습은 전체 원문을 출력하지 않아 민감한 메시지를 재노출하지 않습니다. 원본 로그가 필요한 환경에서는 접근 제한된 파일을 별도로 보관하고 요약에 경로를 연결합니다. 오류 행 수는 원인 분류가 아닌 분석 입력의 품질 지표입니다.
테스트의 경계 사례를 읽습니다
빈 입력, FAIL 없이 OK만 있는 대상, 복구가 없는 실패, 대상이 섞인 역순 입력, 같은 초의 앞뒤 행, 존재하지 않는 날짜를 검사합니다. starter는 유효한 대상의 사건을 집계하지 않으므로 정상 사례부터 실패합니다. 하나의 예제 출력만 하드코딩하면 빈 입력이나 다른 대상에서 깨집니다. ValueError가 날짜 변환 밖으로 나오면 해당 행을 INVALID로 세는 예외 처리 위치를 확인합니다.
타임라인을 원인 설명에 연결합니다
정렬 요약은 언제 무엇을 관측했는지 보여 주지만 왜 실패했는지는 담지 않습니다. 앞 레슨의 권한·이름·inode 근거와 변경 기록을 같은 UTC 축에 연결합니다. 복구 조치 시각과 첫 정상 관측 시각도 별도로 적습니다. 면접에서는 점검 실패를 알리는 방식과 함께 관측 시각·자료 누락·요약의 범위를 설명합니다. 로그 오류 횟수 집계와 압축 파일 처리는 더 읽기로 보내고 여기서는 첫 사건 선택 규칙을 완성합니다.
따라하기
시간과 원래 순서 정렬
고정 UTC 시각과 행 번호를 키로 정렬합니다. 예제는 요약 이전의 순서를 확인합니다.
events=[("2026-10-09T09:03:00Z",0,"OK"),("2026-10-09T09:01:00Z",1,"FAIL"),("2026-10-09T09:00:00Z",2,"OK")]
for at,seq,state in sorted(events): print(at,state)실행 결과
2026-10-09T09:00:00Z OK 2026-10-09T09:01:00Z FAIL 2026-10-09T09:03:00Z OK
첫 실패 이후 정상 선택
실패 전 정상과 이후 정상을 나누는 최소 루프입니다.
states=["OK","FAIL","FAIL","OK"]
first=None
for i,state in enumerate(states):
if state=="FAIL" and first is None: first=i
elif state=="OK" and first is not None:
print("FIRST_INDEX",first,"RECOVERY_INDEX",i)
break실행 결과
FIRST_INDEX 1 RECOVERY_INDEX 3
존재하지 않는 날짜 처리
입력 모양이 같아도 날짜 검증에 실패하면 잘못된 행 수를 증가시킵니다.
from datetime import datetime
invalid=0
try: datetime.strptime("2026-02-30T09:00:00Z","%Y-%m-%dT%H:%M:%SZ")
except ValueError: invalid+=1
print("INVALID",invalid)실행 결과
INVALID 1
확인 문제
실습
표준 입력의 고정 UTC 시각 YYYY-MM-DDTHH:MM:SSZ·공백 없는 대상·OK/FAIL 세 필드를 처리합니다. 공백 줄은 무시하고 형식·날짜·상태 오류 행은 INVALID에 셉니다. 대상 이름 순서로 대상 최초FAIL 복구OK를 출력합니다. 실패 없으면 두 칸 NONE, 실패 이후 정상 없으면 복구 NONE입니다. 같은 시각은 입력 순서를 유지합니다. 최초 사건만 요약하고 마지막 줄에 INVALID 수를 출력합니다.
모범 답안
import sys, re
from datetime import datetime
def summarize(text):
groups={}; invalid=0
for seq,line in enumerate(text.splitlines()):
if not line.strip(): continue
parts=line.split()
if len(parts)!=3:
invalid+=1; continue
at,target,state=parts
if not re.fullmatch(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z',at) or state not in ('OK','FAIL'):
invalid+=1; continue
try: datetime.strptime(at,'%Y-%m-%dT%H:%M:%SZ')
except ValueError:
invalid+=1; continue
groups.setdefault(target,[]).append((at,seq,state))
result=[]
for target in sorted(groups):
first='NONE'; recovered='NONE'
for at,seq,state in sorted(groups[target]):
if state=='FAIL' and first=='NONE': first=at
elif state=='OK' and first!='NONE':
recovered=at; break
result.append(f'{target} {first} {recovered}')
result.append(f'INVALID {invalid}')
return '\n'.join(result)+'\n'
print(summarize(sys.stdin.read()),end='')
더 읽기
면접 질문
- 점검 스크립트가 실패를 알리는 방식을 설명합니다.