데이터와 로그 증가
100분 안팎
학습 목표
du로 큰 경로를 찾고 로그 보관·순환 정책의 적용 전후 사용량을 비교합니다.
개념
어디가 증가하는지 먼저 좁힙니다
블록 경보가 도달했다고 모든 파일을 지우면 서비스 데이터와 장애 분석 기록을 함께 잃을 수 있습니다. df는 파일시스템의 여유를 알려 주고 du는 지정한 경로에서 접근 가능한 파일들의 사용량을 좁히는 도구입니다. 담당자는 data와 로그를 분리해 어느 영역이 얼마나 증가했는지 확인한 뒤 보관 정책을 선택합니다. 이전 모듈의 items.json은 업무 데이터이므로 로그 정리 대상에 포함하지 않습니다.
Linux VM에서 du -x -k -d 1에 전용 실습 경로를 지정합니다. -x는 다른 파일시스템으로 넘어가는 탐색을 줄이고 -d 1은 한 단계 하위의 합계를 보여 줍니다. -k로 단위를 고정한 뒤 sort -n으로 큰 경로를 찾습니다. 처음부터 서버 루트 전체를 뒤지기보다 data가 속한 대상 범위를 정합니다. 권한 오류가 있으면 stderr와 종료 코드를 함께 남기고 숫자를 완전한 합계로 보고하지 않습니다. du 매뉴얼에서 옵션 정의를 확인할 수 있습니다.
논리적 크기와 실제 할당량을 나눕니다
파일 내용의 바이트 합과 디스크에 할당된 블록 합은 다를 수 있습니다. 작은 파일은 내용보다 더 큰 할당 단위를 사용하고 sparse 파일은 논리적 크기보다 적은 블록을 사용할 수 있습니다. 이번 테스트는 실제 파일 내용과 보관 개수를 검사하며 du의 정확한 전후 숫자를 고정하지 않습니다. 비교할 때 동일 도구·옵션·단위·경로를 사용하고 두 측정 사이에 무엇이 생성되거나 삭제되었는지 기록합니다.
du와 df가 일치하지 않는 상황을 단일 원인으로 확정하지 않습니다. 접근하지 못한 디렉터리, 마운트 경계, 파일시스템 메타데이터, 삭제했지만 아직 열린 파일 등이 차이를 만들 수 있습니다. 차이가 크면 측정 범위와 오류부터 확인하고 열린 파일 문제는 기존 프로세스 관찰 절차와 연결합니다. df의 여유가 바로 늘지 않았다고 같은 삭제 명령을 반복해 원문 증거를 지우지 않습니다.
열린 로그 파일을 이름만 삭제하면 작성 프로세스가 기존 파일을 계속 참조할 수 있습니다. 이름을 바꾼 경우도 작성자는 옛 파일을 계속 쓸 수 있으므로 이름 순환만으로 새 로그가 생긴다고 보장하지 않습니다. 이번 실습은 작성자가 파일을 닫은 사본만 순환합니다. 실제 서비스의 파일 재열기 신호나 journal 보관 설정은 별도 변경 작업입니다. 작은 Python 순환 함수를 그대로 운영 로그에 적용하지 않습니다.
보관 정책을 크기와 개수로 정합니다
실습 정책은 활성 app.log와 최근 과거 두 파일 app.log.1, app.log.2를 보관합니다. 최근일수만 정하면 하루의 폭증을 제어하지 못할 수 있고 개수만 정하면 한 파일의 크기가 계속 커질 수 있습니다. 최대 파일 크기·순환 시점·보관 개수를 함께 적습니다. 활성과 보관 파일의 최대 크기가 각각 1MiB라면 논리적 로그 예산은 최대 3MiB이며 데이터·백업·메타데이터 공간은 별도로 남깁니다.
증가 속도는 두 관측값의 차이를 실제 경과 시간으로 나누어 계산합니다. 한 시간에 6MiB 늘고 가용 공간이 24MiB라면 단순 선형 모델의 소진 예상은 4시간입니다. 이 값은 증가가 일정하다는 가정의 예측이며 급증·정리·압축이 발생하면 달라집니다. 관측 시각을 빼고 숫자 차이만 계산하면 시간당 증가라고 부를 수 없습니다. 용량 경보와 소진 예상은 함께 사용하되 서로의 의미를 대신하지 않습니다.
삭제 전에 로그의 보존 목적을 결정합니다. 장애 원문·개인정보·업무 보관 요구는 서로 다른 판단이 필요하며 오늘 큰 파일이라는 이유만으로 정리하지 않습니다. 필요 기록을 승인된 별도 위치에 보관할 때도 그 위치의 여유를 확인합니다. 같은 파일시스템에 단순 복사하면 오히려 부족을 악화시킬 수 있습니다. 이 레슨은 실습 사본의 결정된 보관 개수만 자동화하며 업무 정책 승인까지 자동으로 결정하지 않습니다.
순환은 오래된 것부터 처리합니다
rotate 함수는 숫자 접미사가 있는 기존 보관 파일을 찾아 큰 번호부터 처리합니다. 보관 한도보다 오래된 것은 삭제하고 남은 것은 번호를 하나 올립니다. 이어 활성 app.log를 app.log.1로 옮기고 새 빈 활성 파일을 만듭니다. 오래된 번호부터 처리하면 이름이 겹쳐 최근 내용을 덮어쓰는 사고를 피할 수 있습니다. 파일 이름 정렬은 10과 2의 문자열 순서가 다르므로 숫자로 해석합니다.
keep은 과거 보관 파일 개수이며 활성 파일 개수를 포함하지 않습니다. keep=1이면 활성과 과거 하나만 남습니다. 이번 함수는 1부터 9까지의 정수만 허용하고 0·음수·논리값은 ValueError로 거부합니다. 보관 개수를 잘못 지정했다고 모든 기록을 없애는 동작을 선택하지 않습니다. 테스트는 정확한 파일 내용까지 확인하므로 파일 이름만 맞춘 미완성 구현으로 통과할 수 없습니다.
정리 대상은 app.log와 app.log.양의정수 이름입니다. items.json이나 notes.log는 그대로 보존합니다. 대상 이름의 심볼릭 링크나 디렉터리는 변경하기 전에 거부합니다. 링크를 따라 파일을 열면 실습 범위 밖 파일을 비우거나 덮어쓸 수 있기 때문입니다. 모든 대상 검토를 먼저 마치고 하나라도 이상하면 아무것도 바꾸지 않습니다. 다만 동시 파일 교체를 막는 보안 시스템은 아니므로 다른 작성자가 없는 전용 사본 폴더에서 실행합니다.
정리 성공 뒤 내용과 서비스를 다시 확인합니다
실습 테스트는 event 1부터 event 5까지 차례로 기록하고 매번 닫은 뒤 순환합니다. 마지막에는 활성 파일이 비어 있고 최근 보관 파일에는 event 5, 직전 보관 파일에는 event 4가 남아야 합니다. app.log.3은 없어야 하며 items.json의 내용은 바뀌지 않아야 합니다. 정상 개수뿐 아니라 누가 최근 기록인지 검사하면 역순 보관이나 중복 삭제를 찾을 수 있습니다.
로컬 과제는 임시 폴더에서 실행해 원래 프로젝트 파일을 변경하지 않습니다. VM에서는 20초 서비스가 자연 종료한 뒤 필요한 journal 출력의 사본을 별도 전용 폴더로 보관하고 정책을 적용합니다. 기존 앱은 파일 로그를 직접 생성하지 않으므로 함수가 journal 저장량을 줄여 주는 것처럼 설명하지 않습니다. 정책 적용 후 같은 data와 HTTP /items 응답을 확인하는 일은 미션 검사에 연결합니다.
AssertionError가 나오면 실패한 검사 이름과 실제·기대 내용을 읽습니다. 최근 내용이 event 4인데 기대가 event 5라면 순환 순서나 활성 이름 이동을 확인합니다. 링크 거부 테스트에서 외부 파일이 바뀌었다면 파일 변경 전에 검토하는 순서를 고칩니다. 모든 예외를 무시하거나 보관 폴더 전체를 삭제해 통과시키지 않습니다. 정상뿐 아니라 빈 로그·keep 경계·특이 파일 유형에서도 정책이 유지되어야 합니다.
관측과 정책을 한 장으로 인계합니다
최종 기록에는 정리 대상, 두 측정 시각, du 단위, 전후 사용량, 남은 이름과 내용, 제외한 데이터, 다음 순환 조건을 적습니다. 파일시스템 공간 회복이 실제로 관측되었는지도 df에서 따로 확인합니다. 재발을 줄이려면 순환 주기·생성 상한·여유 경보를 함께 설계합니다. 자세한 du 옵션은 더 읽기로 보내고 이번 레슨에서는 사본의 최근 기록 보존을 실행과 테스트로 완성합니다.
따라하기
보관 예산과 선형 소진 예측
정책 가정에 따른 모델 계산을 실행합니다. 실제 df 관측은 아닙니다.
active_mib, keep = 1, 2
print('log_budget_mib='+str(active_mib * (keep + 1)))
print('hours='+str(24 / 6))실행 결과
log_budget_mib=3 hours=4.0
VM에서 증가 경로 비교
전용 프로젝트 경로만 읽습니다. 오류가 나오면 그대로 남기고 합계가 완전하지 않을 수 있음을 기록합니다.
du -x -k -d 1 /srv/bootcamp-m02/lab-root
df -Pk /srv/bootcamp-m02/lab-root/data로그 사본 순환 구현 검사
별도 실습 ZIP에서 rotate를 완성하고 내용·보관 개수·데이터와 링크 보존을 확인합니다.
bash check.sh순환 뒤 실제 du 감소와 보관 개수 확인
solution과 같은 순환 함수를 임시 폴더에서 실행합니다. du -sk의 실제 전후 할당량을 읽어 감소 여부를 비교하고 과거 두 개·빈 활성 파일을 확인합니다. 할당량의 절대 숫자는 파일시스템마다 달라 고정하지 않습니다.
from pathlib import Path
import re
def rotate(root, keep=2):
"""Writer must be closed. Keep newest numeric archives; never touch data."""
root = Path(root)
if root.is_symlink() or not root.is_dir() or type(keep) is not int or not 1 <= keep <= 9:
raise ValueError('invalid log directory or keep')
active = root / 'app.log'
archives = [p for p in root.iterdir() if re.fullmatch(r'app\.log\.[1-9][0-9]*', p.name)]
for p in [active] + archives:
if p.is_symlink() or (p.exists() and not p.is_file()):
raise ValueError('refuse symlink or non-file')
# Validate everything before changing any file.
for p in sorted(archives, key=lambda p: int(p.name.rsplit('.',1)[1]), reverse=True):
number = int(p.name.rsplit('.',1)[1])
if number >= keep: p.unlink()
else: p.rename(root / ('app.log.' + str(number + 1)))
if active.exists(): active.rename(root / 'app.log.1')
active.touch(mode=0o600)
return sorted(p.name for p in root.iterdir() if p.name == 'app.log' or re.fullmatch(r'app\.log\.[1-9][0-9]*', p.name))
import subprocess, tempfile
with tempfile.TemporaryDirectory() as d:
p = Path(d)
for name in ('app.log', 'app.log.1', 'app.log.2', 'app.log.3'):
(p / name).write_bytes(b'x' * 1048576)
before = int(subprocess.check_output(['du', '-sk', str(p)], text=True).split()[0])
rotate(p, 2)
after = int(subprocess.check_output(['du', '-sk', str(p)], text=True).split()[0])
print('allocated_kib_decreased='+str(after < before))
print('archives='+str(len(list(p.glob('app.log.*')))))
print('active_bytes='+str((p / 'app.log').stat().st_size))
실행 결과
allocated_kib_decreased=True archives=2 active_bytes=0
확인 문제
실습
task.py의 rotate를 완성합니다. 닫힌 전용 사본의 app.log와 숫자 보관 파일만 다룹니다. keep=2는 과거 두 개를 뜻하며 큰 번호부터 처리해 최근 내용을 보존합니다. 다른 파일을 유지하고 링크·디렉터리·잘못된 keep은 모든 변경 전에 거부합니다. 테스트는 보관 내용·개수·빈 시작·keep 경계·링크와 데이터 보존을 검사합니다.
실행 명령
bash check.sh
기대 결과
6 tests·OK·종료 코드 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 디스크 용량이 남았는데 파일 생성이 실패한 원인을 설명합니다.