목차
- Sentry 메모리 누수, 왜 잡기 어려운가
- Sentry 디버깅 관점에서 본 핵심 개념
- 제가 잡았던 실제 패턴
- 실전 구현 1: Sentry에 운영 맥락 심기
- 실전 구현 2: 재현 스크립트와 누수 코드 분리
- 실전 구현 3: 수정 방향과 배포 전략
- ⚠️ 주의사항: 제가 실제로 겪은 삽질 포인트
- 1. ru_maxrss를 실시간 메모리로 착각
- 2. 예외가 없으니 애플리케이션 문제를 늦게 의심
- 3. 디버그 로그가 문제를 키움
- 4. Sentry를 만능 분석기로 기대
- 검증: 해결됐다고 판단한 기준
- 정리: Sentry 메모리 누수 대응 체크리스트
- 자주 묻는 질문
- Sentry만으로 메모리 누수를 찾을 수 있나요?
- 메모리 누수 해결 후 무엇을 가장 먼저 봐야 하나요?
- 클라우드 에러 모니터링만 잘 되어 있으면 충분한가요?
Sentry 메모리 누수 해결: 실전 디버깅 사례 연구
Sentry 메모리 누수 문제는 생각보다 늦게 티가 납니다. 처음에는 응답이 조금씩 느려지는 정도라서 그냥 트래픽이 늘었나 싶거든요. 그런데 어느 순간부터 컨테이너가 재시작되고, OOMKilled(메모리 부족으로 인한 강제 종료)가 찍히고, 에러는 많은데 원인은 안 보이는 상황이 옵니다. 저도 프로덕션 환경에서 딱 그 구간을 겪어봤습니다. 처음엔 애플리케이션 코드보다 인프라 쪽 문제라고 의심했었는데, 실제로 파고 들어가 보니 요청별로 쌓이는 객체 참조가 해제되지 않는 전형적인 메모리 누수였습니다. 이번 글은 Sentry 메모리 누수를 어떻게 추적했고, 어떤 식으로 좁혀 갔는지, 그리고 최종적으로 어떤 기준으로 해결 여부를 검증했는지 정리해 봤습니다.
특히 Sentry 디버깅, 클라우드 에러 모니터링, 메모리 누수 해결, 성능 최적화가 한 번에 엮여 있는 사례라서, 운영 중인 Python API 서버나 컨테이너 환경을 다루는 분들께 꽤 현실적인 참고가 될 거 같습니다.

프로덕션 API 서버, Sentry, 컨테이너 메모리 사용량 흐름을 한눈에 보여주는 개요 이미지입니다.
Sentry 메모리 누수, 왜 잡기 어려운가
쉽게 말해 메모리 누수(memory leak, 더 이상 필요 없는 메모리가 해제되지 않고 계속 남는 현상)는 에러 한 번으로 끝나지 않습니다. 요청은 성공하는데 프로세스 메모리만 조금씩 올라갈 수도 있고, 특정 조건에서만 객체가 쌓일 수도 있죠. 그래서 로그(log, 텍스트 기록)만 봐서는 감이 잘 안 옵니다.
여기서 주목할 포인트가 있습니다. Sentry는 기본적으로 에러 모니터링(error monitoring)과 이벤트 추적(event tracking)에 강점이 있고, 메모리 누수는 증상과 맥락을 묶어 보는 용도로 정말 유용했습니다. 저는 아래 네 가지를 먼저 봤어요.
- 언제 메모리 사용량이 급격히 올라가는지
- 어떤 릴리스(release, 배포 버전) 이후부터 재현되는지
- 어떤 엔드포인트(endpoint, API 경로)에서 현상이 두드러지는지
- 재시작 직전 남기는 경고 이벤트와 예외 스택이 무엇인지
혹시 이런 경험 있으신가요? CPU는 멀쩡한데 메모리만 톱니처럼 올라가고, 재배포하면 잠깐 괜찮아졌다가 다시 터지는 상황이요. 이럴 때는 감으로 고치면 거의 다시 터집니다. 저도 처음엔 캐시(cache) 설정만 바꾸면 되겠지 했는데, 삽질 좀 했습니다 ㅎㅎ
Sentry 디버깅 관점에서 본 핵심 개념
제가 실제로 써보니, 메모리 누수는 도구를 나눠서 보는 게 훨씬 편했어요. Sentry는 현상의 타이밍과 요청 맥락을 모으고, Python 내장 도구는 어떤 객체가 쌓이는지를 확인하는 식이었습니다.
| 도구 | 역할 | 이번 사례에서 한 일 |
|---|---|---|
| Sentry | 이벤트 추적, 릴리스 비교, 환경 분리 | 특정 배포 이후 경고 이벤트 증가와 엔드포인트 상관관계 확인 |
| tracemalloc | 메모리 할당 추적 | 어느 코드 경로에서 객체가 계속 쌓이는지 비교 |
| gc | 가비지 컬렉션 상태 확인 | 참조가 남아 해제되지 않는 객체 패턴 점검 |
| ps/top/kubectl | 운영 레벨 리소스 확인 | 프로세스 RSS 증가, 재시작 시점, OOM 패턴 확인 |
쉽게 말해, Sentry 메모리 누수 대응은 Sentry 하나만으로 끝내는 작업이 아니라, 관측(observability, 시스템 상태를 관찰하는 능력)의 중심축으로 Sentry를 두고 주변 도구를 붙이는 작업입니다.
제가 잡았던 실제 패턴
문제의 원인은 요청 처리 중 생성한 큰 응답 데이터를 전역 리스트(global list)에 디버깅 목적으로 붙여 놓은 코드였습니다. 개발 단계에서는 편했는데, 프로덕션에서 요청이 누적되면서 리스트가 비워지지 않았죠. 더 골치 아픈 건 예외가 바로 나지 않았다는 점입니다. 그래서 클라우드 에러 모니터링만 보고 있으면 늦게 알아차리기 쉬워요.
실전 구현 1: Sentry에 운영 맥락 심기
처음 한 일은 Sentry 이벤트에 운영 맥락을 더 많이 넣는 것이었습니다. 그냥 SDK만 붙여 두면 예외는 잘 모이는데, Sentry 메모리 누수처럼 느린 장애는 맥락이 부족할 때가 많거든요.
- 릴리스(release)와 환경(environment)을 분리합니다.
- 의심되는 엔드포인트에 breadcrumb(브레드크럼, 이벤트 전 단계 기록)를 남깁니다.
- 메모리 사용량이 임계치에 가까워지면 warning 이벤트를 직접 보냅니다.
- 컨테이너 재시작 전후 로그와 Sentry 타임라인을 맞춰 봅니다.
import os
import time
import resource
import sentry_sdk
from sentry_sdk import capture_message, set_tag, add_breadcrumb
from sentry_sdk.integrations.logging import LoggingIntegration
logging_integration = LoggingIntegration(
level=None,
event_level=None,
)
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"),
environment=os.environ.get("APP_ENV", "production"),
release=os.environ.get("APP_RELEASE", "unknown"),
integrations=[logging_integration],
traces_sample_rate=0.1,
)
MEMORY_WARN_MB = 700
def get_rss_mb():
rss_kb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if os.uname().sysname == "Darwin":
return rss_kb / 1024 / 1024
return rss_kb / 1024
def observe_request(path: str, request_id: str):
set_tag("endpoint", path)
set_tag("request_id", request_id)
add_breadcrumb(
category="request",
message=f"handling {path}",
level="info",
data={"request_id": request_id, "ts": int(time.time())},
)
rss_mb = get_rss_mb()
if rss_mb >= MEMORY_WARN_MB:
capture_message(
f"high memory usage detected: {rss_mb:.1f}MB",
level="warning",
)
여기서 중요한 포인트! Sentry에 경고 이벤트를 직접 남기면, 예외가 나기 전 단계의 흐름이 보입니다. 저는 이걸 넣고 나서야 특정 API 요청이 몰린 뒤 10~20분 정도 지나면 경고가 늘어난다는 걸 확인했어요.

Sentry에서 release, environment, endpoint 태그를 어떻게 나눠서 보는지 설명하는 구성 이미지입니다.
실전 구현 2: 재현 스크립트와 누수 코드 분리
운영에서만 보이는 문제라도, 결국 로컬이나 스테이징(staging, 사전 검증 환경)에서 비슷하게 재현할 수 있어야 고칠 수 있죠. 저는 홈랩에서도 비슷하게 많이 해보는데, 재현이 되는 순간부터 속도가 확 올라갑니다.
문제를 단순화한 예시는 이런 식이었습니다.
from fastapi import FastAPI
app = FastAPI()
DEBUG_CACHE = []
@app.get("/items")
def items():
payload = {"rows": ["x" * 10000 for _ in range(500)]}
# 문제 코드: 요청마다 큰 객체를 전역 리스트에 보관
DEBUG_CACHE.append(payload)
return {"count": len(payload["rows"]), "cache_size": len(DEBUG_CACHE)}
이런 코드는 테스트 몇 번 할 때는 티가 안 나요. 근데 프로덕션에서 요청이 계속 들어오면 얘기가 달라집니다. 그래서 부하를 살짝 줘서 반복 요청을 만들었습니다.
for i in $(seq 1 300); do
curl -s http://127.0.0.1:8000/items > /dev/null
done
ps -o pid,rss,command -p $(pgrep -f uvicorn)
이후 tracemalloc으로 전후 차이를 찍었습니다.
import tracemalloc
import linecache
tracemalloc.start(25)
# 부하 실행 전 snapshot
before = tracemalloc.take_snapshot()
# 여기서 반복 호출 수행
after = tracemalloc.take_snapshot()
stats = after.compare_to(before, "lineno")
for stat in stats[:10]:
frame = stat.traceback[0]
print(f"{frame.filename}:{frame.lineno} -> {stat.size_diff / 1024:.1f} KiB")
print(linecache.getline(frame.filename, frame.lineno).strip())
실제로 써보니까 이 방식이 좋았던 이유는 명확합니다. Sentry에서 어느 요청 흐름이 이상한지를 찾고, tracemalloc으로 어느 코드 줄이 증가하는지를 고정할 수 있었거든요. 두 도구가 따로 노는 게 아니라 딱 이어졌습니다.
실전 구현 3: 수정 방향과 배포 전략
수정 자체는 의외로 단순했습니다. 전역에 보관하던 디버그 데이터를 없애고, 꼭 필요하면 크기를 제한한 ring buffer(링 버퍼, 고정 길이 순환 버퍼)로 바꿨어요. 사실 운영 서버에서는 요청 본문이나 큰 응답 객체를 오래 들고 있는 패턴 자체를 피하는 게 맞습니다.
from collections import deque
from fastapi import FastAPI
app = FastAPI()
DEBUG_CACHE = deque(maxlen=20)
@app.get("/items")
def items():
payload = {"rows": ["x" * 10000 for _ in range(500)]}
# 필요한 메타데이터만 제한적으로 저장
DEBUG_CACHE.append({
"row_count": len(payload["rows"]),
})
return {"count": len(payload["rows"]), "cache_size": len(DEBUG_CACHE)}
배포할 때도 한 번에 전체 트래픽을 넘기지 않았습니다. 제가 운영할 때는 이런 문제는 꼭 점진 배포(rolling deployment, 순차 배포)로 갑니다. 왜냐하면 메모리 누수는 수정했다고 끝이 아니라, 정말 증가 추세가 꺾였는지를 봐야 하거든요.
- 수정 버전을 새 릴리스로 배포합니다.
- Sentry에서 이전 릴리스와 새 릴리스를 분리해 봅니다.
- 동일 엔드포인트 기준 warning 이벤트 발생 빈도를 비교합니다.
- 컨테이너 재시작 횟수와 RSS 증가 그래프를 같이 봅니다.
재현 스크립트, 문제 코드, 수정 후 구조를 비교해서 보여주는 디버깅 흐름 이미지입니다.
⚠️ 주의사항: 제가 실제로 겪은 삽질 포인트
여기서부터가 진짜 중요합니다. 메모리 누수는 원인을 하나만 보면 놓치는 경우가 많아요.
1. ru_maxrss를 실시간 메모리로 착각
ru_maxrss는 최대 RSS 값이라서 순간 메모리 스냅샷처럼 쓰면 해석이 꼬일 수 있습니다. 저도 처음엔 현재 메모리라고 생각하고 봤다가 그래프 해석을 잘못했었어요. 그래서 운영에서는 ps, 컨테이너 메트릭, 노드 메모리 지표를 같이 봐야 합니다.
2. 예외가 없으니 애플리케이션 문제를 늦게 의심
OOMKilled는 쿠버네티스(Kubernetes)나 런타임 차원의 이벤트로 보이는 경우가 많아서, 애플리케이션 버그와 분리해서 생각하기 쉬워요. 근데 여기서 중요한 건, 예외가 없다고 누수가 없는 게 아니다라는 점입니다.
3. 디버그 로그가 문제를 키움
디버깅하려고 요청 본문, 응답 객체, ORM 결과를 통째로 메모리에 들고 있으면 오히려 누수를 악화시킬 수 있습니다. 특히 전역 변수, 캐시, 클로저(closure, 외부 변수를 참조하는 함수), 콜백 등록 구조는 한 번 더 의심해 보셔야 합니다.
4. Sentry를 만능 분석기로 기대
Sentry는 정말 강력한 도구지만, heap dump 분석기나 프로파일러(profiler)를 완전히 대체하지는 않습니다. 저는 Sentry를 상황판으로 쓰고, 세부 원인 분석은 tracemalloc과 코드 리뷰로 마무리했어요. 이 조합이 제일 현실적이었습니다.
- ✅ Sentry: 릴리스, 환경, 요청 흐름, 경고 이벤트 연계
- ✅ 시스템 도구: RSS, 재시작, 컨테이너 상태 확인
- ✅ 언어 도구: 객체 증가 경로 추적
- ⚠️ 단일 지표만 보고 결론 내리기 금지
검증: 해결됐다고 판단한 기준
드디어 됐다! 라고 말하기 전에, 저는 꼭 기준을 숫자가 아니라 패턴으로 봅니다. 구체적인 절대 수치는 환경마다 다르니까요. 대신 아래 기준은 어느 환경에서도 꽤 유효합니다.
- 동일 트래픽 조건에서 프로세스 메모리가 계속 우상향하지 않는지
- 문제 엔드포인트 호출 후에도 일정 시간 뒤 메모리 증가 추세가 평탄해지는지
- Sentry warning 이벤트 빈도가 줄었는지
- OOM 재시작이 사라졌는지
- 새 릴리스 이후 동일 이슈 그룹이 다시 생기지 않는지
저는 수정 전후를 이런 식으로 운영 점검했습니다.
kubectl get pods -n prod
kubectl top pods -n prod
kubectl describe pod api-server-xxxxx -n prod
# 애플리케이션 로그와 Sentry 이벤트 시점을 함께 대조
kubectl logs deployment/api-server -n prod --since=30m
결과적으로 수정 후에는 특정 API 호출이 몰려도 메모리 그래프가 계속 계단식으로 올라가지 않았고, 재시작도 멈췄습니다. Sentry에서도 문제 릴리스 기준으로 쌓이던 경고 이벤트가 눈에 띄게 줄었어요. 이런 순간이 있죠. 아, 이건 증상만 눌러 놓은 게 아니라 원인을 제대로 건드렸구나 하는 느낌이요. 이거 진짜 편하더라고요.

수정 전후 메모리 사용량 추세와 Sentry 이벤트 변화를 비교하는 결과 대시보드 이미지입니다.
정리: Sentry 메모리 누수 대응 체크리스트
마지막으로, 제가 다시 같은 상황을 만나면 이 순서대로 갑니다. 독자분들도 북마크해 두시면 꽤 쓸 만하실 거 같습니다.
| 단계 | 체크 포인트 | 목적 |
|---|---|---|
| 1 | Sentry에 release, environment, endpoint 태그 확인 | 문제 범위 좁히기 |
| 2 | 경고 이벤트 수동 전송 추가 | 예외 전 단계 포착 |
| 3 | 부하 재현과 tracemalloc 비교 | 코드 라인 단위 확인 |
| 4 | 전역 객체, 캐시, 콜백 참조 점검 | 누수 패턴 제거 |
| 5 | 점진 배포 후 Sentry와 메트릭 비교 | 해결 여부 검증 |
정리해 보면, Sentry 메모리 누수 대응의 핵심은 Sentry를 단순 에러 수집기로 쓰지 않는 데 있습니다. 릴리스 비교, 경고 이벤트, 요청 태그를 잘 심어 두면 메모리 누수처럼 느리고 애매한 장애도 꽤 빨리 좁혀 갈 수 있거든요. 저도 처음엔 헷갈렸는데, 결국 답은 비슷했습니다. 증상을 구조화해서 보고, 재현하고, 작은 단서부터 묶어 가는 것. 운영은 늘 그렇게 풀리더라고요.
다음 글에서는 OpenTelemetry(오픈텔레메트리, 분산 추적 표준)와 Sentry를 함께 붙여서 요청 지연과 에러를 한 번에 보는 흐름도 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 집계 파이프라인과 같이 보시면 더 이해가 잘 되실 거 같습니다.
자주 묻는 질문
Sentry만으로 메모리 누수를 찾을 수 있나요?
완전히 단독으로 찾기보다는, 어느 흐름에서 문제가 커지는지를 좁히는 데 강합니다. 실제 누수 지점은 tracemalloc, gc, 코드 리뷰 같은 보조 수단이 필요할 때가 많아요.
메모리 누수 해결 후 무엇을 가장 먼저 봐야 하나요?
같은 트래픽에서 메모리 사용량이 계속 증가하는지, 컨테이너 재시작이 멈췄는지, 그리고 Sentry 디버깅 기준으로 동일 릴리스 이슈가 줄었는지를 먼저 보시면 됩니다.
클라우드 에러 모니터링만 잘 되어 있으면 충분한가요?
충분하지 않을 때가 많습니다. 메모리 누수는 인프라 이벤트처럼 보이지만 실제 원인은 코드 참조 구조인 경우가 많아서, 애플리케이션 계층과 시스템 계층을 같이 보셔야 해요.

Sentry, tracemalloc, 시스템 메트릭을 조합한 대응 순서를 한 장으로 요약한 이미지입니다.
![[Cloud] Sentry 메모리 누수 해결: 실전 디버깅 사례 연구](https://blog.pswq.net/wp-content/uploads/2026/08/sentry-production-memory-leak-debugging-case-study-thumbnail.jpg)
![[k8s] Karpenter 오토스케일링, 성능 벤치마크: 다양한 워크로드 환경 테스트](https://blog.pswq.net/wp-content/uploads/2026/08/karpenter-autoscaling-performance-benchmark-thumbnail.jpg)




![[Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석](https://blog.pswq.net/wp-content/uploads/2026/08/karpenter-node-provisioning-failure-debugging-case-thumbnail.jpg)





![[3D Printer] 3D 프린팅 후처리 마스터하기: 표면 처리부터 도색까지 완벽 가이드](https://blog.pswq.net/wp-content/uploads/2026/08/3d-printing-post-processing-master-guide-surface-finishing-painting-thumbnail.jpg)





![[AI] 클라우드 AI 비용 비교: AWS SageMaker vs Google Cloud Vertex AI](https://blog.pswq.net/wp-content/uploads/2026/08/cloud-ai-platform-cost-efficiency-aws-sagemaker-google-ai-platform-thumbnail.jpg)




![[AI] RAG 시스템 구축, ChromaDB 선택 가이드 및 실제 적용 사례](https://blog.pswq.net/wp-content/uploads/2026/08/chromadb-rag-system-selection-and-application-case-study-thumbnail.jpg)





![[홈랩] Proxmox ARM64 홈랩 1년 회고: 라즈베리 파이 5 기반 운영 경험과 교훈](https://blog.pswq.net/wp-content/uploads/2026/08/proxmox-arm64-homelab-raspberry-pi-5-retrospective-thumbnail.jpg)





![[Proxmox] Proxmox 스냅샷 7가지 핵심 전략: 백업 분리와 안전한 운영을 위한 베스트 프랙티스](https://blog.pswq.net/wp-content/uploads/2026/08/proxmox-snapshot-best-practices-data-loss-prevention-thumbnail.jpg)




![[OpenStack] 오픈스택 비용 분석: 프로젝트별 자원 사용량 기반 차지백 모델](https://blog.pswq.net/wp-content/uploads/2026/08/openstack-project-resource-cost-analysis-chargeback-thumbnail.jpg)



![[OpenStack] 오픈스택 운영 회고: 안정성, 비용, 그리고 인프라 관리의 핵심](https://blog.pswq.net/wp-content/uploads/2026/08/openstack-3year-operation-retrospective-lessons-thumbnail.jpg)

