본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 27. · 0 Views
실전 트러블슈팅 완벽 가이드
메일 전송 실패부터 디스크 용량 관리까지, 운영 환경에서 실제로 마주치는 문제들을 해결하는 방법을 알아봅니다. 초급 개발자도 당황하지 않고 체계적으로 문제를 진단하고 해결할 수 있도록 실무 경험을 담았습니다.
목차
1. 메일 전송 실패 디버깅
월요일 아침, 김개발 씨의 슬랙에 긴급 메시지가 도착했습니다. "회원가입 인증 메일이 안 간다고 CS팀에서 난리예요!" 로그를 확인해보니 메일 전송 실패 에러가 수백 개씩 쌓여 있었습니다.
대체 무엇이 문제였을까요?
메일 전송 실패는 SMTP 설정 오류, 인증 문제, 네트워크 차단 등 다양한 원인으로 발생합니다. 마치 편지를 보내려는데 우체국 주소를 잘못 적거나, 우표를 안 붙이거나, 우체통이 막혀있는 것과 같습니다.
체계적인 디버깅 순서를 알면 빠르게 원인을 찾아 해결할 수 있습니다.
다음 코드를 살펴봅시다.
import smtplib
import ssl
from email.mime.text import MIMEText
def debug_mail_sending(smtp_host, smtp_port, username, password, recipient):
# 1단계: SMTP 서버 연결 테스트
try:
context = ssl.create_default_context()
with smtplib.SMTP(smtp_host, smtp_port, timeout=10) as server:
server.set_debuglevel(1) # 디버그 모드 활성화
server.starttls(context=context)
# 2단계: 인증 테스트
server.login(username, password)
# 3단계: 메일 발송 테스트
msg = MIMEText("테스트 메일입니다.")
msg['Subject'] = '메일 전송 테스트'
msg['From'] = username
msg['To'] = recipient
server.sendmail(username, recipient, msg.as_string())
print("메일 전송 성공!")
except smtplib.SMTPAuthenticationError as e:
print(f"인증 실패: {e}")
except smtplib.SMTPConnectError as e:
print(f"연결 실패: {e}")
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 그동안 메일 기능은 "그냥 작동하는 것"이라고 생각했습니다.
하지만 오늘, 메일 시스템이 멈추자 모든 것이 달라졌습니다. 선배 개발자 박시니어 씨가 다가왔습니다.
"메일 문제는 무작정 코드를 뒤지기 전에, 단계별로 확인해야 해요. 마치 의사가 진찰하듯이 말이죠." 그렇다면 메일 전송 실패를 어떻게 체계적으로 디버깅할까요?
메일 전송은 크게 세 단계로 나눌 수 있습니다. 첫째는 SMTP 서버 연결, 둘째는 인증, 셋째는 실제 발송입니다.
마치 택배를 보내려면 먼저 택배 회사에 연락하고, 본인 확인을 하고, 마지막으로 물건을 맡기는 것과 같습니다. 가장 흔한 문제는 SMTP 서버 연결 실패입니다.
방화벽이 25번이나 587번 포트를 막고 있거나, SMTP 서버 주소가 잘못되었을 수 있습니다. 클라우드 환경에서는 보안상 아웃바운드 SMTP 포트가 기본적으로 차단되어 있는 경우가 많습니다.
두 번째로 많은 문제는 인증 실패입니다. 비밀번호가 틀렸거나, 구글이나 네이버 같은 서비스에서 "보안 수준이 낮은 앱"의 접근을 차단했을 수 있습니다.
요즘은 대부분 앱 비밀번호를 별도로 생성해서 사용해야 합니다. 위 코드에서 핵심은 set_debuglevel(1) 부분입니다.
이 한 줄을 추가하면 SMTP 통신의 모든 과정이 콘솔에 출력됩니다. 서버가 어떤 응답을 보내는지, 어느 단계에서 실패하는지 한눈에 알 수 있습니다.
try-except 블록을 보면 SMTPAuthenticationError와 SMTPConnectError를 분리해서 처리하고 있습니다. 이렇게 하면 "연결 자체가 안 되는 건지, 연결은 되는데 로그인이 안 되는 건지" 정확히 구분할 수 있습니다.
실무에서는 AWS SES, SendGrid 같은 메일 전송 서비스를 많이 사용합니다. 이런 서비스들은 대시보드에서 발송 현황과 실패 원인을 확인할 수 있어 디버깅이 훨씬 수월합니다.
다시 김개발 씨 이야기로 돌아가 봅시다. 디버그 로그를 켜보니 "535 Authentication failed"라는 메시지가 보였습니다.
확인해보니 지난주 보안팀에서 SMTP 비밀번호를 변경했는데, 환경변수 업데이트를 깜빡한 것이었습니다.
실전 팁
💡 - SMTP 디버그 로그는 민감 정보가 포함될 수 있으니 프로덕션에서는 반드시 비활성화하세요
- 클라우드 환경에서는 SES, SendGrid 같은 전용 서비스 사용을 권장합니다
- 메일 발송 실패 시 재시도 로직과 알림 시스템을 함께 구축하세요
2. 스팸함 분류 문제 해결
"저희 뉴스레터가 스팸함에 들어간다는 문의가 계속 들어와요." 마케팅팀 이대리 씨의 하소연에 김개발 씨는 고개를 갸웃했습니다. 분명 메일은 정상적으로 발송되는데, 왜 스팸으로 분류되는 걸까요?
메일이 스팸함에 들어가는 것은 발송 자체의 문제가 아니라 신뢰도 문제입니다. 마치 처음 보는 번호로 전화가 오면 스팸으로 의심하는 것처럼, 메일 서버도 발신자의 신뢰도를 다양한 기준으로 평가합니다.
SPF, DKIM, DMARC라는 세 가지 인증 설정이 핵심입니다.
다음 코드를 살펴봅시다.
import dns.resolver
import subprocess
def check_email_authentication(domain):
results = {}
# SPF 레코드 확인 (발신 서버 인증)
try:
spf = dns.resolver.resolve(domain, 'TXT')
for record in spf:
if 'v=spf1' in str(record):
results['SPF'] = str(record)
except Exception as e:
results['SPF'] = f"없음: {e}"
# DKIM 선택자 확인 (메일 서명 검증)
dkim_selector = 'default' # 일반적인 선택자
try:
dkim = dns.resolver.resolve(f'{dkim_selector}._domainkey.{domain}', 'TXT')
results['DKIM'] = str(list(dkim)[0])
except Exception:
results['DKIM'] = "설정 필요"
# DMARC 정책 확인 (인증 실패 시 처리 방법)
try:
dmarc = dns.resolver.resolve(f'_dmarc.{domain}', 'TXT')
results['DMARC'] = str(list(dmarc)[0])
except Exception:
results['DMARC'] = "설정 필요"
return results
# 사용 예시
auth_status = check_email_authentication("example.com")
for key, value in auth_status.items():
print(f"{key}: {value}")
김개발 씨는 이메일 인증에 대해 처음 들어봤습니다. "그냥 메일 보내면 되는 거 아니에요?" 박시니어 씨가 웃으며 설명을 시작했습니다.
"요즘 스팸 메일이 얼마나 많은지 알지? 하루에 수십억 통이 오가는데, 대부분이 스팸이야.
그래서 메일 서버들이 까다로워진 거야." SPF(Sender Policy Framework)는 "이 도메인에서 메일을 보낼 수 있는 서버 목록"입니다. 마치 회사에서 "우리 직원만 이 명함을 쓸 수 있어요"라고 공지하는 것과 같습니다.
DNS에 TXT 레코드로 등록합니다. DKIM(DomainKeys Identified Mail)은 메일에 디지털 서명을 추가하는 것입니다.
편지에 봉인을 하는 것처럼, 메일이 중간에 위변조되지 않았음을 증명합니다. 발신 서버의 개인키로 서명하고, 수신 서버가 DNS에 공개된 공개키로 검증합니다.
DMARC(Domain-based Message Authentication, Reporting & Conformance)는 SPF나 DKIM 인증에 실패했을 때 어떻게 처리할지 정책을 정하는 것입니다. "인증 실패하면 스팸함에 넣어라" 또는 "아예 거부해라" 같은 지시를 내릴 수 있습니다.
위 코드는 특정 도메인의 이메일 인증 설정 상태를 확인합니다. dns.resolver를 사용해 각각의 DNS 레코드를 조회하고, 설정되어 있는지 확인합니다.
DKIM의 경우 선택자(selector)가 서비스마다 다르므로 주의가 필요합니다. 실무에서는 MXToolbox나 mail-tester.com 같은 온라인 도구를 사용하면 더 상세한 진단을 받을 수 있습니다.
점수와 함께 무엇을 개선해야 하는지 친절하게 알려줍니다. 스팸 필터링에 영향을 주는 다른 요소도 있습니다.
메일 본문에 "무료", "긴급", "당첨" 같은 스팸성 단어가 많으면 점수가 깎입니다. HTML 메일의 경우 이미지만 있고 텍스트가 거의 없어도 의심받습니다.
김개발 씨는 DNS 설정을 확인해보니 SPF만 설정되어 있고 DKIM과 DMARC는 없었습니다. 인프라팀과 협력하여 세 가지 인증을 모두 설정한 후, 스팸 분류 문제가 크게 줄었습니다.
실전 팁
💡 - mail-tester.com에서 테스트 메일을 보내면 10점 만점으로 스팸 점수를 확인할 수 있습니다
- 새 도메인은 신뢰도가 낮으므로 처음부터 대량 발송하지 마세요
- 수신 거부 링크를 반드시 포함하고, 실제로 작동하게 만드세요
3. 블랙리스트 등재 대응
어느 날 갑자기 모든 메일이 반송되기 시작했습니다. 에러 메시지에는 낯선 단어가 보였습니다.
"Your IP is listed on Spamhaus..." 김개발 씨는 처음 듣는 이름에 당황했습니다. 블랙리스트란 대체 무엇일까요?
블랙리스트는 스팸 메일을 보내는 것으로 알려진 IP 주소나 도메인의 목록입니다. 마치 신용불량자 명단처럼, 한번 등재되면 메일 발송에 심각한 제약이 생깁니다.
하지만 원인을 파악하고 조치를 취하면 해제를 요청할 수 있습니다.
다음 코드를 살펴봅시다.
import dns.resolver
import socket
# 주요 블랙리스트 목록
BLACKLISTS = [
'zen.spamhaus.org',
'bl.spamcop.net',
'b.barracudacentral.org',
'dnsbl.sorbs.net',
'spam.dnsbl.sorbs.net',
]
def check_ip_blacklist(ip_address):
# IP 주소를 역순으로 변환 (예: 1.2.3.4 -> 4.3.2.1)
reversed_ip = '.'.join(reversed(ip_address.split('.')))
results = {}
for blacklist in BLACKLISTS:
query = f"{reversed_ip}.{blacklist}"
try:
# DNS 조회가 성공하면 블랙리스트에 등재된 것
dns.resolver.resolve(query, 'A')
results[blacklist] = "등재됨 - 해제 요청 필요!"
except dns.resolver.NXDOMAIN:
results[blacklist] = "정상"
except Exception as e:
results[blacklist] = f"확인 불가: {e}"
return results
# 현재 서버 IP 확인 및 블랙리스트 체크
server_ip = "203.0.113.50" # 실제 서버 IP로 교체
print(f"서버 IP {server_ip} 블랙리스트 확인 결과:")
for bl, status in check_ip_blacklist(server_ip).items():
print(f" {bl}: {status}")
박시니어 씨가 심각한 표정으로 말했습니다. "블랙리스트에 올라갔다는 건 우리 서버가 스팸 발송지로 찍혔다는 거야.
원인을 빨리 찾아야 해." 블랙리스트는 전 세계 메일 서버들이 참조하는 "위험한 발신지 목록"입니다. Spamhaus, SpamCop, Barracuda 등 여러 조직에서 각자의 기준으로 블랙리스트를 운영합니다.
마치 여러 신용평가사가 각자의 기준으로 신용점수를 매기는 것과 비슷합니다. 블랙리스트에 등재되는 주요 원인은 세 가지입니다.
첫째, 서버가 해킹당해 실제로 스팸을 발송한 경우. 둘째, 사용자가 대량 메일을 보냈는데 수신자들이 스팸 신고를 많이 한 경우.
셋째, 같은 IP 대역의 다른 서버가 스팸을 보내 연좌제로 등재된 경우입니다. 위 코드의 핵심은 IP 역순 변환입니다.
블랙리스트 조회는 특이하게도 IP를 거꾸로 뒤집어서 DNS 질의를 합니다. 예를 들어 1.2.3.4를 확인하려면 4.3.2.1.zen.spamhaus.org를 조회합니다.
응답이 오면 등재된 것이고, NXDOMAIN(존재하지 않음) 응답이 오면 정상입니다. 등재되었다면 어떻게 해야 할까요?
먼저 원인을 반드시 찾아 제거해야 합니다. 원인을 해결하지 않고 해제만 요청하면 다시 등재될 뿐입니다.
서버 보안 점검, 메일 발송 로그 분석, 오픈 릴레이 여부 확인 등을 수행합니다. 원인을 제거했다면 각 블랙리스트 사이트에서 **해제 요청(delisting request)**을 합니다.
대부분 웹사이트에서 IP를 입력하고 해제 사유를 작성하면 됩니다. Spamhaus 같은 곳은 자동으로 해제되는 경우도 있지만, 시간이 걸릴 수 있습니다.
실무에서는 MXToolbox Blacklist Check를 많이 사용합니다. 수십 개의 블랙리스트를 한 번에 확인할 수 있어 편리합니다.
정기적으로 모니터링하여 등재 시 알림을 받도록 설정하는 것도 좋습니다. 김개발 씨의 경우, 조사 결과 마케팅팀에서 구매한 외부 메일 리스트로 대량 발송을 했고, 이것이 스팸 신고로 이어져 블랙리스트에 등재된 것이었습니다.
해당 리스트 사용을 중단하고 해제 요청을 한 후, 일주일 만에 정상화되었습니다.
실전 팁
💡 - 정기적으로 블랙리스트 등재 여부를 모니터링하세요
- 절대 구매한 메일 리스트를 사용하지 마세요 - 블랙리스트 등재의 주요 원인입니다
- 클라우드 서비스의 공유 IP보다 전용 IP 사용을 고려하세요
4. 인증서 만료 문제
금요일 저녁 6시, 퇴근 준비를 하던 김개발 씨의 휴대폰이 울렸습니다. "사이트에 접속이 안 돼요!
브라우저에서 위험하다고 경고가 떠요!" 확인해보니 SSL 인증서가 어제 만료되어 있었습니다. 왜 아무도 몰랐을까요?
SSL/TLS 인증서는 HTTPS 통신을 가능하게 하는 디지털 증명서입니다. 마치 여권처럼 유효기간이 있어서, 만료되면 브라우저가 해당 사이트를 위험하다고 경고합니다.
자동 갱신 설정과 만료 모니터링이 필수입니다.
다음 코드를 살펴봅시다.
import ssl
import socket
from datetime import datetime, timedelta
import subprocess
def check_ssl_expiry(hostname, port=443):
"""SSL 인증서 만료일 확인"""
context = ssl.create_default_context()
try:
with socket.create_connection((hostname, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
# 만료일 파싱
expire_date = datetime.strptime(
cert['notAfter'], '%b %d %H:%M:%S %Y %Z'
)
days_left = (expire_date - datetime.now()).days
return {
'hostname': hostname,
'issuer': dict(x[0] for x in cert['issuer']),
'expires': expire_date.strftime('%Y-%m-%d'),
'days_left': days_left,
'status': '긴급!' if days_left < 7 else
'주의' if days_left < 30 else '정상'
}
except Exception as e:
return {'hostname': hostname, 'error': str(e)}
# 여러 도메인 점검
domains = ['example.com', 'api.example.com', 'admin.example.com']
for domain in domains:
result = check_ssl_expiry(domain)
print(f"{result['hostname']}: {result.get('days_left', 'N/A')}일 남음 - {result.get('status', result.get('error'))}")
박시니어 씨가 한숨을 쉬었습니다. "인증서 만료는 예방할 수 있는 장애야.
캘린더에 만료일 적어두거나 자동 갱신을 설정했어야지." SSL 인증서는 웹사이트의 신원을 증명하고 통신을 암호화하는 디지털 문서입니다. 마치 운전면허증이 "이 사람이 운전할 자격이 있다"를 증명하듯, SSL 인증서는 "이 사이트가 진짜 그 사이트가 맞다"를 증명합니다.
인증서에는 유효기간이 있습니다. 예전에는 2-3년짜리도 있었지만, 요즘은 보안상의 이유로 최대 13개월로 제한됩니다.
유효기간이 지나면 브라우저가 "이 사이트는 안전하지 않습니다"라는 경고를 표시하고, 대부분의 사용자는 겁먹고 떠나버립니다. 위 코드는 SSL 인증서의 만료일을 확인합니다.
socket과 ssl 모듈을 사용해 서버에 연결하고, 인증서 정보를 가져옵니다. getpeercert() 함수가 인증서의 상세 정보를 딕셔너리로 반환하는데, 여기서 notAfter 필드가 만료일입니다.
만료일까지 남은 일수를 계산해서 7일 미만이면 "긴급", 30일 미만이면 "주의"로 표시합니다. 이런 스크립트를 cron으로 매일 실행하고, 결과를 슬랙이나 이메일로 받으면 만료를 미리 알 수 있습니다.
Let's Encrypt를 사용하면 무료로 인증서를 발급받을 수 있고, Certbot으로 자동 갱신을 설정할 수 있습니다. certbot renew 명령어가 만료 30일 전에 자동으로 갱신을 시도합니다.
대부분의 서버에서 cron에 등록해두면 됩니다. AWS Certificate Manager(ACM)나 Cloudflare 같은 서비스를 사용하면 인증서 관리가 더 편해집니다.
자동 갱신은 물론이고, 여러 도메인을 한 번에 관리할 수 있습니다. 주의할 점은 인증서 갱신 후 웹 서버 재시작이 필요할 수 있다는 것입니다.
Certbot은 자동으로 처리하지만, 수동 설치한 경우 nginx reload나 apache reload를 잊지 마세요. 김개발 씨는 그날 밤 인증서를 갱신하고, Certbot 자동 갱신을 설정했습니다.
그리고 만료 30일 전에 슬랙 알림을 보내는 모니터링 스크립트도 추가했습니다. 이제 다시는 이런 일이 없을 것입니다.
실전 팁
💡 - Let's Encrypt + Certbot으로 자동 갱신을 설정하면 걱정이 없습니다
- 와일드카드 인증서(*.example.com)를 사용하면 서브도메인 관리가 편합니다
- 만료 알림은 30일, 14일, 7일, 1일 전에 여러 번 보내도록 설정하세요
5. 디스크 용량 관리
"서버가 느려요." 김개발 씨가 접속해보니 응답 속도가 평소의 10배였습니다. 원인을 찾다가 df -h를 실행했더니 눈이 휘둥그레졌습니다.
루트 파티션 사용률 99%. 로그 파일이 디스크를 가득 채우고 있었습니다.
디스크 용량 부족은 예고 없이 찾아오는 서버 장애의 주범입니다. 마치 창고가 가득 차면 새 물건을 넣을 수 없듯이, 디스크가 가득 차면 로그 기록, 데이터 저장, 심지어 프로세스 실행도 실패합니다.
정기적인 모니터링과 자동 정리가 해결책입니다.
다음 코드를 살펴봅시다.
import shutil
import os
import subprocess
from datetime import datetime, timedelta
def check_disk_usage(threshold_percent=80):
"""디스크 사용량 확인 및 경고"""
total, used, free = shutil.disk_usage("/")
used_percent = (used / total) * 100
result = {
'total_gb': total // (1024**3),
'used_gb': used // (1024**3),
'free_gb': free // (1024**3),
'used_percent': round(used_percent, 1),
'status': '위험!' if used_percent > 90 else
'경고' if used_percent > threshold_percent else '정상'
}
return result
def find_large_files(path="/var/log", min_size_mb=100):
"""대용량 파일 찾기"""
large_files = []
for root, dirs, files in os.walk(path):
for file in files:
filepath = os.path.join(root, file)
try:
size = os.path.getsize(filepath)
if size > min_size_mb * 1024 * 1024:
large_files.append({
'path': filepath,
'size_mb': round(size / (1024**2), 1)
})
except (OSError, PermissionError):
continue
return sorted(large_files, key=lambda x: x['size_mb'], reverse=True)[:10]
# 사용 예시
disk = check_disk_usage()
print(f"디스크: {disk['used_gb']}GB / {disk['total_gb']}GB ({disk['used_percent']}%) - {disk['status']}")
print("\n대용량 파일 TOP 10:")
for f in find_large_files():
print(f" {f['size_mb']}MB - {f['path']}")
디스크가 100% 차면 어떤 일이 벌어질까요? 박시니어 씨가 설명했습니다.
"새로운 데이터를 쓸 수 없으니 DB 트랜잭션이 실패하고, 로그도 못 쓰니 디버깅도 어려워져. 심지어 SSH 접속이 안 되는 경우도 있어." 디스크 용량 문제의 주범은 대부분 로그 파일입니다.
애플리케이션 로그, 웹 서버 로그, 시스템 로그가 쉼 없이 쌓입니다. 특히 에러가 많이 발생하는 상황에서는 로그가 기하급수적으로 늘어납니다.
위 코드의 check_disk_usage 함수는 shutil.disk_usage를 사용해 디스크 상태를 확인합니다. 사용률이 80%를 넘으면 경고, 90%를 넘으면 위험으로 표시합니다.
이 함수를 cron으로 주기적으로 실행하고, 임계값을 넘으면 알림을 보내도록 설정합니다. find_large_files 함수는 지정된 경로에서 대용량 파일을 찾습니다.
os.walk로 디렉토리를 순회하면서 파일 크기를 확인하고, 지정된 크기(기본 100MB) 이상인 파일만 수집합니다. 어떤 파일이 공간을 차지하고 있는지 한눈에 파악할 수 있습니다.
로그 파일 관리의 핵심은 logrotate입니다. 리눅스에 기본 설치되어 있으며, /etc/logrotate.d/ 디렉토리에 설정 파일을 추가하면 됩니다.
일정 크기나 기간이 지나면 로그를 압축하고, 오래된 것은 삭제합니다. 임시 파일도 범인이 될 수 있습니다.
/tmp, /var/tmp, 애플리케이션의 캐시 디렉토리 등을 확인하세요. Docker를 사용한다면 docker system prune으로 사용하지 않는 이미지와 컨테이너를 정리할 수 있습니다.
장기적인 해결책은 모니터링 시스템 구축입니다. Prometheus + Grafana, Datadog, CloudWatch 등을 사용하면 디스크 사용량 추이를 그래프로 확인하고, 임계값 초과 시 자동 알림을 받을 수 있습니다.
김개발 씨는 로그 파일들을 정리하고, logrotate 설정을 추가했습니다. 그리고 디스크 사용률이 80%를 넘으면 슬랙으로 알림을 받도록 설정했습니다.
다시는 갑작스러운 디스크 풀로 당황하지 않을 것입니다.
실전 팁
💡 - 디스크 사용률 80%를 넘기 전에 미리 정리하거나 용량을 늘리세요
- journalctl --vacuum-size=500M으로 시스템 로그 크기를 제한할 수 있습니다
- ncdu 명령어를 설치하면 대화형으로 디스크 사용량을 분석할 수 있습니다
6. 성능 병목 현상 분석
"요즘 API 응답이 왜 이렇게 느려요?" 사용자 불만이 쌓이자 팀장님이 김개발 씨에게 원인 분석을 맡겼습니다. 코드는 안 바꿨는데 왜 갑자기 느려졌을까요?
성능 병목을 찾는 여정이 시작되었습니다.
성능 병목이란 시스템에서 전체 속도를 제한하는 가장 느린 부분입니다. 마치 고속도로의 요금소처럼, 아무리 도로가 넓어도 좁은 병목 구간에서 정체가 발생합니다.
CPU, 메모리, 디스크 I/O, 네트워크, 데이터베이스 중 어디가 병목인지 찾는 것이 첫 번째 단계입니다.
다음 코드를 살펴봅시다.
import psutil
import time
import subprocess
def analyze_system_bottleneck():
"""시스템 리소스 병목 분석"""
analysis = {}
# CPU 사용률 (1초간 측정)
cpu_percent = psutil.cpu_percent(interval=1, percpu=True)
analysis['cpu'] = {
'average': round(sum(cpu_percent) / len(cpu_percent), 1),
'per_core': cpu_percent,
'status': '병목!' if max(cpu_percent) > 90 else '정상'
}
# 메모리 사용률
memory = psutil.virtual_memory()
analysis['memory'] = {
'total_gb': round(memory.total / (1024**3), 1),
'used_percent': memory.percent,
'available_gb': round(memory.available / (1024**3), 1),
'status': '병목!' if memory.percent > 90 else '정상'
}
# 디스크 I/O (초당 읽기/쓰기)
disk_before = psutil.disk_io_counters()
time.sleep(1)
disk_after = psutil.disk_io_counters()
analysis['disk_io'] = {
'read_mb_s': round((disk_after.read_bytes - disk_before.read_bytes) / (1024**2), 1),
'write_mb_s': round((disk_after.write_bytes - disk_before.write_bytes) / (1024**2), 1),
}
# 가장 CPU를 많이 사용하는 프로세스
processes = [(p.info['name'], p.info['cpu_percent'])
for p in psutil.process_iter(['name', 'cpu_percent'])]
analysis['top_processes'] = sorted(processes, key=lambda x: x[1], reverse=True)[:5]
return analysis
# 분석 실행
result = analyze_system_bottleneck()
print(f"CPU: 평균 {result['cpu']['average']}% - {result['cpu']['status']}")
print(f"메모리: {result['memory']['used_percent']}% 사용 - {result['memory']['status']}")
print(f"디스크 I/O: 읽기 {result['disk_io']['read_mb_s']}MB/s, 쓰기 {result['disk_io']['write_mb_s']}MB/s")
print("CPU 사용량 TOP 5:", result['top_processes'])
박시니어 씨가 화이트보드에 그림을 그렸습니다. "성능 문제를 해결하려면 먼저 어디가 병목인지 찾아야 해.
추측하지 말고 측정해." 병목 현상은 시스템에서 가장 느린 부분이 전체 성능을 결정하는 현상입니다. 마치 파이프에서 가장 좁은 부분이 물의 흐름을 결정하는 것과 같습니다.
아무리 다른 부분을 개선해도 병목이 해결되지 않으면 전체 성능은 나아지지 않습니다. 병목의 주요 후보는 네 가지입니다.
CPU가 100%에 가까우면 연산 능력이 부족한 것입니다. 메모리가 부족하면 스왑이 발생하여 급격히 느려집니다.
디스크 I/O가 한계에 달하면 읽기/쓰기 요청이 대기합니다. 네트워크가 포화되면 외부 통신이 지연됩니다.
위 코드는 psutil 라이브러리를 사용해 시스템 리소스를 측정합니다. CPU는 코어별 사용률을 확인하고, 메모리는 전체 사용률과 가용량을 체크합니다.
디스크 I/O는 초당 읽기/쓰기 바이트를 계산합니다. 가장 중요한 정보 중 하나는 어떤 프로세스가 리소스를 많이 사용하는지입니다.
process_iter로 모든 프로세스의 CPU 사용률을 가져와 정렬하면 범인을 쉽게 찾을 수 있습니다. 웹 애플리케이션에서 흔한 병목은 데이터베이스입니다.
느린 쿼리, 인덱스 부재, 커넥션 풀 고갈 등이 원인일 수 있습니다. MySQL의 slow query log나 PostgreSQL의 pg_stat_statements를 활용하면 문제 쿼리를 찾을 수 있습니다.
APM(Application Performance Monitoring) 도구를 사용하면 더 정밀한 분석이 가능합니다. New Relic, Datadog APM, Elastic APM 등이 있으며, 함수별 실행 시간, 외부 호출 지연 등을 시각화해줍니다.
성능 문제는 "갑자기" 발생한 것처럼 보이지만, 대부분 점진적으로 악화되다가 임계점을 넘은 것입니다. 따라서 평소에 기준선(baseline)을 측정해두고, 현재 상태와 비교하는 것이 중요합니다.
김개발 씨는 분석 결과 메모리 사용률이 95%를 넘고 있었습니다. 확인해보니 최근 배포된 기능에서 메모리 누수가 발생하고 있었습니다.
해당 코드를 수정하자 응답 속도가 정상으로 돌아왔습니다.
실전 팁
💡 - "느리다"는 주관적입니다. 항상 숫자로 측정하고 비교하세요
- htop, iotop, nethogs 같은 도구로 실시간 모니터링할 수 있습니다
- 성능 테스트는 프로덕션과 유사한 환경에서 해야 의미가 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
ClamAV 바이러스 스캔 완벽 가이드
리눅스 서버에서 ClamAV를 활용한 바이러스 스캔 시스템을 구축하는 방법을 다룹니다. 메일 서버와 연동하여 악성코드를 자동으로 탐지하고 처리하는 실무 노하우를 배워봅니다.
스팸 필터링 완벽 가이드 SpamAssassin과 Rspamd 실전 활용
이메일 서버 운영에서 필수적인 스팸 필터링 기술을 다룹니다. SpamAssassin과 Rspamd의 차이점부터 베이지안 필터, 블랙리스트 연동, 커스텀 룰 작성까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.
LDAP 인증 연동 완벽 가이드
LDAP을 활용한 중앙 집중식 인증 시스템 구축 방법을 알아봅니다. OpenLDAP 설정부터 Postfix, Dovecot 연동, 그리고 Active Directory 통합까지 실무에서 바로 적용할 수 있는 내용을 다룹니다.
Docker 환경 준비 및 docker-mailserver 설치
나만의 메일 서버를 Docker로 구축하는 방법을 처음부터 끝까지 안내합니다. docker-mailserver를 활용하여 실무에서 바로 사용할 수 있는 메일 시스템을 단계별로 설정해봅니다.
메일 서버 아키텍처 설계 완벽 가이드
메일 서버를 직접 구축하고 운영하기 위한 아키텍처 설계 방법을 다룹니다. MTA, MDA부터 고가용성 설계까지 실무에서 필요한 모든 것을 초급자도 이해할 수 있도록 쉽게 설명합니다.