낭비를 찾는 사용량 보고서
70분 안팎
학습 목표
유휴 시간과 자원 크기를 근거로 정리 후보를 찾습니다.
개념
삭제보다 판단을 먼저 자동화합니다
CPU가 낮은 자원을 보자마자 삭제하면 장애 복원용 데이터까지 없어질 수 있습니다. 사용량 보고서는 낭비가 의심되는 대상을 좁혀 사람이 확인할 근거를 만드는 도구입니다. 안내판의 후보 실행·복원용 volume·다른 팀 자원을 같은 표로 읽되 소유 범위와 보존 이유를 먼저 확인합니다. 이 과제의 CANDIDATE는 삭제 완료 상태가 아닙니다. 보고서 생성기는 어떤 자원도 중지하거나 지우지 않습니다. 결과는 다음 레슨의 백업·의존성 검토에 넘깁니다.
관측 시간의 의미를 고정합니다
입력 resources의 hours는 그 자원을 관측한 시간, idle_hours는 동일 기간에 교육 정책상 유휴로 분류한 시간입니다. 모두 0 이상인 정수이며 idle_hours는 hours를 넘지 않습니다. 기간을 다르게 수집한 두 값을 비교하지 않습니다. 실제 운영에서는 유휴 정의에 요청량·배치 시간·사용 목적을 함께 정해야 하지만 이 실습은 고정된 값을 제공합니다. CPU 표본 하나로 idle_hours를 만들지 않습니다. 단위와 관측 범위가 입력 계약에 있어야 사람이 합계의 의미를 설명할 수 있습니다.
보존 규칙에 우선순위를 줍니다
개인 프로젝트 행에서 retain이 true이면 유휴 시간이 길어도 KEEP입니다. false이고 idle_hours가 6 이상이면 CANDIDATE이며 나머지는 KEEP입니다. 6시간은 교육용 분기 값이고 권장 운영 임계값이 아닙니다. 프로젝트 값이 다르면 OTHER로 분류하며 정리 후보에 넣지 않습니다. 조건을 if 순서로 표현할 때 소유 범위와 보존을 앞에 두면 유휴 임계값이 데이터 보존 규칙을 덮지 않습니다. 경계인 6은 포함하고 5는 포함하지 않습니다.
합계가 대표하는 시간을 구분합니다
출력은 CANDIDATE 개수와 해당 idle_hours 합, KEEP 개수와 해당 hours 합, OTHER 개수와 해당 hours 합의 세 줄입니다. 첫 줄만 유휴 시간을 합산하므로 세 줄의 시간을 더하여 전체 관측 시간이라고 부르면 틀립니다. 서로 동시에 존재한 자원들의 시간 합은 자원시간이지 벽시계 기간이 아닙니다. 두 자원을 각각 10시간 관측하면 합계 20은 20시간짜리 장애나 운영 기간을 뜻하지 않습니다. 소수 가격을 곱하지 않으며 이 합계를 실제 청구액으로 제시하지 않습니다.
JSON 계약을 먼저 검사합니다
최상위는 project 문자열과 resources 배열을 가진 객체입니다. 각 행에는 id·project 문자열, hours·idle_hours 정수, retain 불리언이 필요합니다. id는 입력 전체에서 중복될 수 없습니다. 같은 자원의 표본 여러 개를 받아 합산하는 과제가 아니므로 중복을 발견하면 INVALID를 출력합니다. 다른 프로젝트 행도 자료 형식 검사에서는 제외하지 않습니다. 형식이 깨진 자료가 섞이면 세 줄 합계가 신뢰할 수 없기 때문입니다. 오류 위치를 숨겨 정상 집계처럼 반환하지 않습니다.
불리언과 정수를 구별합니다
Python에서 bool은 int의 하위 형식이므로 isinstance(True, int)는 참입니다. 관측 시간에 true가 들어온 입력을 1시간으로 받아들이면 자료 계약이 무너집니다. 이번 구현은 type(value) is int로 시간 필드를 검사합니다. retain은 반대로 type(value) is bool이어야 합니다. 문자열 false는 빈 문자열이 아니어서 조건식에서 참으로 취급될 수 있으므로 자동 변환하지 않습니다. 입력을 임의로 수정하는 대신 INVALID로 거부하고 생산 측에서 자료를 고치게 합니다.
빈 자료와 빈 입력을 나눕니다
아무 문자도 입력하지 않으면 EMPTY 한 줄입니다. 정상 객체에 resources가 빈 배열이면 세 줄 모두 개수와 시간이 0입니다. 전자는 자료가 제공되지 않았다는 상태이고 후자는 빈 목록이 제공됐다는 상태입니다. JSON 파싱이 실패하거나 필수 필드가 없으면 INVALID 한 줄로 끝납니다. 프로그램의 디버그 print가 stdout에 섞이면 채점 결과가 달라지므로 최종 보고서만 출력합니다. 이 구분은 자료 수집 실패와 자원 0을 혼동하지 않도록 연습하는 장치입니다.
조건식부터 작은 사례로 검증합니다
retain=true·idle_hours=20인 volume을 먼저 생각합니다. 이 행은 KEEP이므로 후보 시간을 늘리지 않습니다. retain=false·idle_hours=6인 후보 container는 CANDIDATE입니다. 같은 자료에서 project만 바꾸면 OTHER이며 retain과 유휴값은 삭제 권한을 주지 않습니다. 이런 단일 행을 검증한 다음 여러 행의 개수와 시간 합을 확인합니다. 큰 자료에서 나온 합계만 보는 것보다 규칙 경계의 한 행이 잘못된 분기를 쉽게 드러냅니다. 정답 코드는 판단 근거를 읽을 수 있는 순서로 작성합니다.
로컬 사용량과 클라우드 비용을 연결합니다
실제 클라우드에서는 계정의 청구·사용량 화면에서 기간·서비스·프로젝트 태그 필터를 확인하고 원천 보고서의 집계 시각을 적습니다. 여기서는 특정 서비스 메뉴 이름이나 현재 요금을 외우지 않습니다. 과금 단위와 할인·보존 스토리지·전송 범위를 확인하기 전에는 로컬 시간 합을 비용으로 바꾸지 않습니다. 프로젝트·환경·책임 역할 태그는 비용의 귀속을 설명하는 기준이 됩니다. 태그가 적용되지 않은 자원은 미분류로 남겨 담당과 생성 경로를 조사합니다.
예산 알림의 역할을 적습니다
예산 알림을 구성한다면 대상 계정·기간·범위·수신 역할·첫 확인 행동을 운영 문서에 씁니다. 알림을 받았다는 사실과 과금 자원이 중지됐다는 사실은 다릅니다. 알림 도착 지연과 보고서 집계 지연도 확인하고 사용량 차단 동작을 별도로 검증하지 않았다면 있다고 주장하지 않습니다. 이번 실습에는 실제 예산 API 호출이 없으므로 실행 출력도 만들지 않습니다. 인계 자료에는 담당이 어느 화면과 필터에서 비용을 확인할지, 초과 징후에 무엇을 조사할지를 작성합니다.
후보의 다음 행동을 제안합니다
보고서에는 후보 이름·기간·보존 여부·유휴 근거와 제안 행동을 함께 적습니다. 요청량이 없는 후보 container는 배포 시연 완료 여부를 확인할 수 있고 복원 volume은 복원 검사가 끝나도 보존 정책 확인이 필요합니다. 크기를 줄이는 제안에는 관측 기간과 재측정 기준을 붙입니다. 후보가 0이어도 낭비가 전혀 없다고 결론 내리지 않고 측정하지 않은 디스크·전송 범위를 한계로 적습니다. 입력에 없는 담당 승인이나 복원 성공을 보고서가 만들어 넣지 않습니다.
출력 차이로 구현을 고칩니다
CANDIDATE의 개수는 맞는데 시간만 틀리면 hours와 idle_hours를 바꾸어 더했는지 읽습니다. KEEP이 줄고 후보가 늘면 retain 우선순위를 점검합니다. INVALID 기대에 숫자가 나오면 불리언·음수·중복 검사 누락을 찾습니다. EMPTY 기대에 예외 traceback이 나오면 파싱 전에 공백 입력을 처리했는지 확인합니다. 테스트 입력을 분기별로 나누고 구현을 고칩니다. CSV로 실제 자료를 읽고 파일 보고서로 저장하는 방법은 더 읽기로 연결하며 여기서는 표준 입력 계약과 판단 정책을 완성합니다.
따라하기
보존 우선과 경계 실행
유휴가 길어도 보존이 우선하는지 비교합니다.
for idle,retain in [(12,True),(6,False),(5,False)]:
state='KEEP' if retain or idle<6 else 'CANDIDATE'
print(idle,retain,state)실행 결과
12 True KEEP 6 False CANDIDATE 5 False KEEP
관측 시간과 유휴 합계
후보 줄에서 두 시간 필드를 혼동하지 않습니다.
rows=[{'hours':10,'idle_hours':6},{'hours':12,'idle_hours':8}]
print('CANDIDATE',len(rows),sum(r['idle_hours'] for r in rows))
print('observed_resource_hours',sum(r['hours'] for r in rows))실행 결과
CANDIDATE 2 14 observed_resource_hours 22
정수 형식의 함정
불리언이 정수의 하위 형식임을 직접 확인하고 계약을 선택합니다.
for value in [6,True,'6',0]:
print(repr(value),isinstance(value,int),type(value) is int)실행 결과
6 True True True True False '6' False False 0 True True
브라우저 테스트로 완성
아래 과제의 JSON 입력과 출력 계약을 읽습니다. EMPTY·빈 배열·보존 우선·6시간 경계·다른 프로젝트·오류 사례의 기대값을 비교하고 starter의 집계 분기를 구현합니다. 실행 버튼의 결과가 tests의 세 줄 또는 한 줄과 일치해야 합니다.
# 아래 실습의 입력·테스트·모범 답안을 비교합니다.확인 문제
실습
표준 입력은 project 문자열과 resources 배열을 가진 JSON 객체입니다. 각 행의 id·project는 비어 있지 않은 문자열, hours·idle_hours는 정수이며 0 <= idle_hours <= hours, retain은 bool입니다. id는 입력 전체에서 유일합니다. 다른 프로젝트 행도 형식 검사를 합니다. 개인 자원은 retain=true이면 KEEP, false이고 idle_hours가 6 이상이면 CANDIDATE, 나머지는 KEEP입니다. 다른 프로젝트는 OTHER입니다. 출력 순서는 CANDIDATE 개수 유휴시간합, KEEP 개수 관측시간합, OTHER 개수 관측시간합입니다. 빈 표준 입력은 EMPTY, 계약 위반·파싱 오류는 INVALID 한 줄입니다. 추가 필드는 무시합니다. 분류는 정리 후보 보고서이며 삭제 명령을 실행하지 않습니다.
모범 답안
import json,sys
def report(text):
if not text.strip():return 'EMPTY'
try:
data=json.loads(text)
if not isinstance(data,dict) or not isinstance(data.get('project'),str) or not data['project'] or not isinstance(data.get('resources'),list):raise ValueError()
buckets={'CANDIDATE':[0,0],'KEEP':[0,0],'OTHER':[0,0]};seen=set()
for r in data['resources']:
if not isinstance(r,dict):raise ValueError()
for k in ['id','project']:
if not isinstance(r.get(k),str) or not r[k]:raise ValueError()
if r['id'] in seen:raise ValueError()
seen.add(r['id'])
h,idle=r.get('hours'),r.get('idle_hours')
if type(h) is not int or type(idle) is not int or not 0<=idle<=h or type(r.get('retain')) is not bool:raise ValueError()
if r['project']!=data['project']:kind='OTHER'
elif r['retain'] or idle<6:kind='KEEP'
else:kind='CANDIDATE'
buckets[kind][0]+=1
buckets[kind][1]+=idle if kind=='CANDIDATE' else h
return '\n'.join(f'{k} {v[0]} {v[1]}' for k,v in buckets.items())
except (ValueError,TypeError,KeyError):return 'INVALID'
print(report(sys.stdin.read()))
더 읽기
면접 질문
- 실습 자원을 안전하게 정리하는 순서를 설명합니다.