fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • Docling — IBM Research 의 문서 파싱 toolkit 상세 정리
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • apartment 페이지로 이동
    • 구리 럭키아파트 24평 인테리어 레퍼런스 모음
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 디지털 채널 면접: 슬롯 도메인 경험을 커머스 도메인 설계 능력으로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 도메인 모델링: 주문·재고·노출의 세 축을 분리해서 설계하기
    • 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Spring Batch vs Event-Driven — 같은 비동기처럼 보이지만 전혀 다른 두 패러다임
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • [초안] DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
    • [초안] 시니어 백엔드를 위한 SLO와 Error Budget 기반 장애 대응
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • investing 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] 커머스/F&B 면접 답변집 — 슬롯 도메인 경험을 주문·결제·쿠폰·매장 설계로 매핑하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS 가 안 줄어드는 이유 — gc.collect 의 한계와 malloc_trim
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
    • [초안] Spring Security 6.x OAuth2 + JWT 상용 인증 설계 — Grant 선택, Resource Server, Refresh Rotation, 로그아웃
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/task/Python 서버 RSS 가 안 줄어들어 m…
system

Python 서버 RSS 가 안 줄어들어 malloc_trim 을 박은 이야기

진행 기간: 2026.05 > 개념 정리는 Python 서버의 RSS 가 안 줄어드는 이유 — gc.collect 의 한계와 malloctrim 참고. 본 글은 그 개념을 실제 운영 환경에 적용한 작업기. 문서 파싱 API 의 운영을 보다가 워커 프로세스의 실제 메모리 사용량(RSS)이 시간당 약 1.4 GB 씩 증가하는 패턴을 발견했다. PDF·PPTX·...

2026.05.22·7 min read·2 views

진행 기간: 2026.05

개념 정리는 Python 서버의 RSS 가 안 줄어드는 이유 — gc.collect 의 한계와 malloc_trim 참고. 본 글은 그 개념을 실제 운영 환경에 적용한 작업기.

배경

문서 파싱 API 의 운영을 보다가 워커 프로세스의 실제 메모리 사용량(RSS)이 시간당 약 1.4 GB 씩 증가하는 패턴을 발견했다. PDF·PPTX·HWP 같은 문서를 Docling 파이프라인으로 markdown 으로 변환하는 서버고, ProcessPoolExecutor 로 띄운 워커가 요청을 처리한다.

운영에서는 이 누수를 막기 위해 MAX_TASKS_PER_WORKER=3 으로 박아두고 있었다. 워커가 3 작업만 처리하면 강제로 죽고 새로 spawn 한다. OS 가 죽은 프로세스의 모든 메모리를 회수하니 RSS 가 다시 깨끗해진다. 단순한 방어책인데, 매번 워밍업 비용이 발생한다 — 새 워커는 모델 가중치 로드와 cache 초기화를 처음부터 다시 한다.

처음에는 "그냥 gc.collect() 를 자주 부르면 되지 않나" 라고 안일하게 생각했다. 코드를 보니 이미 청크 처리 후마다 호출하고 있었다. 그런데도 RSS 가 줄지 않았다. 왜 안 줄어드는지 진단하면서 glibc malloc 의 동작을 다시 읽게 됐고, 그 결과로 release_unused_memory() 라는 helper 를 만들어 8 곳의 gc.collect() 호출을 일괄 교체했다.

본 글은 그 진단·결정·검증 과정을 정리한 기록이다.

gc.collect() 가 RSS 를 안 줄이는 이유

먼저 짚어둘 것 — CPython 의 gc.collect() 는 OS 메모리 반환과 무관하다. 두 가지만 한다.

  1. 참조 카운트 사이클 (cyclic reference) 정리
  2. cyclic GC 가 모은 garbage 회수

회수된 객체는 결국 C 의 free() 로 반환되지만, 그 반환처가 OS 가 아니다. 두 단계의 캐시 계층을 거친다.

  • (a) Python 의 pymalloc — 512 바이트 이하 객체. arena pool 에 보관
  • (b) glibc 의 ptmalloc2 — 그 이상. bin 자료구조에 보관

(b) 단계의 free chunk 는 glibc 안에 머무르고 OS 의 RSS 카운터에는 그대로 남는다. gc.collect() 를 아무리 자주 불러도 이 계층을 비우는 호출이 아니라서 효과가 없다.

glibc 의 메모리 할당 전략

glibc 의 malloc() 은 요청 크기에 따라 두 경로로 갈린다.

  • 작은 청크 (< M_MMAP_THRESHOLD, 기본 128 KiB): brk(2) / sbrk(2) 로 확장된 heap 영역에 배치. 우리가 흔히 "프로세스의 heap" 이라고 부르는 영역
  • 큰 청크 (≥ 임계값): mmap(2) 으로 별도 영역을 잡아 단독 chunk 로 둔다

큰 청크가 free 될 때는 munmap(2) 으로 OS 에 즉시 반환된다. 그래서 큰 텐서나 이미지 버퍼처럼 단일 할당이 큰 객체는 해제 직후 RSS 가 잘 줄어든다. 문제는 작은 청크다. brk heap 안에서 free 된 청크는 OS 로 안 가고 glibc 의 bin 자료구조 — fastbin, smallbin, largebin, unsorted bin — 중 하나로 들어간다.

heap 의 최상단(top chunk)에 인접한 연속 free 영역이 충분히 자랐을 때만 자동 트림이 발동한다. 임계값은 M_TRIM_THRESHOLD (기본 128 KiB) 이고, top chunk 가 이 값을 넘으면 free() 가 내부적으로 sbrk(-N) 으로 heap 을 줄인다. 단 — heap 중간에 갇힌 free chunk 는 트림 대상이 아니다. 이게 단편화(fragmentation)다.

우리 서버의 상황을 가설로 정리

진단 단계에서 내가 세운 가설은 다음과 같다.

  • Docling 파이프라인이 페이지마다 작은 버퍼를 수없이 alloc/free 한다 (이미지 cell, OCR 영역, 텍스트 chunk 등)
  • 이 중 상당수가 M_MMAP_THRESHOLD 미만이라 brk heap 으로 간다
  • 작업이 끝나도 heap 중간에 free chunk 가 흩어져 단편화. top chunk 가 충분히 자라지 않아 자동 트림 발동 조건을 못 만난다
  • gc.collect() 는 Python 객체만 정리하고 이 단편화에는 손도 못 댄다
  • 결과: RSS 단조 증가 → MAX_TASKS_PER_WORKER=3 의 워커 강제 종료만이 회수 수단

가설은 가설이지만, 적어도 "gc.collect() 만으로 안 풀린다" 는 결론은 확실했다.

malloc_trim(0) 의 정확한 동작

glibc 매뉴얼 (man 3 malloc_trim) 인용:

attempts to release free memory from the heap (by calling sbrk(2) or madvise(2) with suitable arguments).

  • 인자 pad 는 heap top 에 남겨둘 여유 공간. 0 이면 한 페이지(4 KiB)만 남기고 모두 반환
  • 반환 값: 실제 OS 로 반환했으면 1, 못 했으면 0
  • 메인 아레나는 sbrk(-N) 으로, 스레드 아레나는 madvise(MADV_DONTNEED) 로 페이지 단위 반환

핵심 제약 — 단편화가 있으면 효과가 제한된다. heap 최상단의 연속 free 영역만 회수 대상이고, 중간에 갇힌 free chunk 는 그대로 남는다. 그래도 자동 트림보다 적극적으로 회수를 시도하므로 정기 호출 가치는 있다.

glibc 2.8 이후로는 메인 아레나 top 외에도 모든 아레나를 순회하며 page-aligned whole free page 가 있는 chunk 도 madvise 로 반환한다. 우리 운영 컨테이너는 그 이후 버전이라 이 동작도 기대할 수 있다.

helper 함수 설계

gc.collect() 8 곳을 그냥 malloc_trim 으로 바꿀 수도 있었지만, "메모리 회수" 의도를 한 함수에 캡슐화하는 편이 향후 변경 (예: CUDA 메모리 해제 추가) 에 유리하다고 봤다. 그래서 util/memory.py 를 신설하고 release_unused_memory() 라는 한 함수로 묶었다.

python
# util/memory.py — 개념 설명용 의사코드
import ctypes, gc, logging, os, sys
 
_log = logging.getLogger(__name__)
_ENABLE_MALLOC_TRIM = os.environ.get("ENABLE_MALLOC_TRIM", "true").lower() == "true"
_IS_LINUX = sys.platform == "linux"
 
try:
    _libc = ctypes.CDLL("libc.so.6") if (_IS_LINUX and _ENABLE_MALLOC_TRIM) else None
except OSError as e:
    _log.warning("libc.so.6 로드 실패 (malloc_trim 비활성): %s", e)
    _libc = None
 
def release_unused_memory() -> None:
    gc.collect()
    if _libc is not None:
        try:
            _libc.malloc_trim(0)
        except Exception as e:
            _log.warning("malloc_trim 호출 실패: %s", e, exc_info=True)

설계 결정 몇 가지를 메모로 남겨둔다.

  • 모듈 로드 시 1회 분기 — 런타임마다 sys.platform 체크하는 비용을 회피. mac 로컬 개발 환경에서는 _libc = None 이 되어 noop
  • env 토글 (ENABLE_MALLOC_TRIM, 기본 true) — 운영 사고 시 즉시 비활성할 수 있는 hot config. 트림 자체가 일으킨 회귀가 의심되면 컨테이너 재시작 없이 끄려고 했지만, 결국 env 변경 자체가 재시작을 요구한다는 한계는 있다
  • mallopt(M_TRIM_THRESHOLD) 임계값 낮춤은 기각 — 매 free() 마다 자동 적용되어 호출 overhead. 우리 patch 의 명시 호출(청크 단위) 이 비용 통제하기 쉽다

ca901 카나리에서 검증

검증은 한 대(ca901)만 swap-container.sh 로 새 이미지(TEST_2026.05.21-3) 로 교체한 뒤 진행했다. drain 으로 LB 격리 후 180 호출 (30 라운드 × 6 sample, 동시 6) 부하 테스트.

시점kr 워커 RSS 합 (GB)restart_kr
T0 baseline13.200
부하 5분13.880
부하 6분12.731 (첫 워커 자연 종료)
부하 10분13.336 (모두 1회씩 종료)
부하 종료 직후12.396
안정화12.396

결과 정리:

  • 180/180 HTTP 200, 회귀 0건
  • RSS 가 13~14 GB 사이 진동, 단조 증가 패턴은 나타나지 않았다
  • cgroup memory 한도 27g (32GB 호스트의 85%) 미발동, RAM_RESTART_THRESHOLD=80% 도 미발동

검증의 한계

부하 테스트 동안 MAX_TASKS_PER_WORKER=20 으로 올려뒀다. 이렇게 하면 워커가 20 작업마다 자연 종료해 그 시점에 OS 가 메모리를 회수한다. 즉, RSS 가 안정적이었던 게 plan009 의 release_unused_memory() 단독 효과인지, max_tasks 종료 효과인지 격리하지 못했다.

엄밀히 격리하려면 MAX_TASKS_PER_WORKER=50 이상으로 올려 워커가 죽기 전 누적 효과를 봐야 한다. 그건 별도 사이클로 미뤘다. 적어도 회귀 0건은 확인됐고, 단편화로 트림 효과가 제한되더라도 추가 안전망(--memory cgroup 한도) 이 작동하니 운영 배포 가능하다고 판단했다.

같은 함정에 빠진 다른 사례들

조사 중에 알게 된 사실 — 이 패턴은 Python 진영에서 잘 알려진 함정이다.

  • uWSGI / Gunicorn 의 max-requests 옵션, Celery 의 worker_max_tasks_per_child 가 모두 같은 문제의 우회책이다. 우리의 MAX_TASKS_PER_WORKER=3 도 본질은 동일하다
  • Polars 처럼 Rust 의 자체 allocator (jemalloc / mimalloc) 를 쓰는 라이브러리는 glibc malloc_trim 영향권 밖이다. 별도 API 필요
  • PyTorch / NumPy 같은 C-extension 은 큰 텐서는 mmap 경로로 가서 free 시 즉시 반환되지만, 작은 메타데이터 버퍼는 brk heap 에 누적된다

Adam Johnson (Django 코어 컨트리뷰터) 의 글이 "결국 워커 재활용이 가장 단순하고 예측 가능하다" 고 결론지은 게 인상적이었다. 우리는 워커 재활용(MAX_TASKS=3)에 더해 malloc_trim 까지 박은 셈인데, 그 둘 사이의 정량적 효과 비교는 아직 못 했다.

지금 보면

helper 함수로 묶은 결정은 옳았지만, 검증 설계는 약했다. MAX_TASKS=20 으로 부하를 돌린 시점에 "trim 단독 효과를 격리할 수 없는 설정" 임을 충분히 인지하지 못했다. 검증 결과가 좋아 보였던 건 trim 과 워커 재활용이 함께 일하면서 어느 쪽이 얼마나 기여했는지 분리되지 않았기 때문이다.

malloc_trim 도 만능이 아니다. 단편화가 심한 워크로드라면 호출해도 RSS 가 잘 안 줄어든다. 운영에서 정말 의미 있게 동작하는지는 MAX_TASKS_PER_WORKER 를 단계적으로 올리면서 (3 → 10 → 20 → 50) RSS 추세를 비교해야 알 수 있다. 그건 다음 사이클의 숙제로 남겨뒀다.

코드보다 "왜 그게 안 통하는가" 에 대한 답을 손에 쥐는 게 더 큰 수확이었다. gc.collect() 를 부르면 메모리가 회수된다는 흔한 직관이 어디서 깨지는지 — pymalloc 의 arena, glibc 의 bin, brk vs mmap, 그리고 단편화 — 를 한 번 정리해두면 다음에 비슷한 증상을 만났을 때 진단 출발선이 달라진다.

참고

  • malloc_trim(3) — Linux manual page
  • mallopt(3) — Linux manual page
  • Malloc Internals and You — Red Hat Developer
  • Run Python Applications Efficiently With malloc_trim — Software at Scale
  • Stop Python from Hoarding Memory with One Extra Step — Medium
  • Working Around Memory Leaks in Your Django Application — Adam Johnson
  • GLibc malloc internal: arena, bin, chunk and sub heap — jipanyang
  • HN discussion: Run Python Applications Efficiently with malloc_trim
on this page
  • 01배경
  • 02gc.collect() 가 RSS 를 안 줄이는 이유
  • 03glibc 의 메모리 할당 전략
  • 04우리 서버의 상황을 가설로 정리
  • 05malloc_trim(0) 의 정확한 동작
  • 06helper 함수 설계
  • 07ca901 카나리에서 검증
  • 검증의 한계
  • 08같은 함정에 빠진 다른 사례들
  • 09지금 보면
  • 10참고

이런 글도

  • 슬롯 당첨 계산 — Decorator 체인 + 우선순위 정렬
    진행 기간: 슬롯 엔진 추상화 작업과 병행 (2025 하반기) 슬롯의 "당첨 금액 계산"은 단순해 보이지만, 실제론 여러 단계의 조합이다. 기본 배당, 프리게임 배수, 프로그레시브 보너스, 멀티플라이어 심볼, 구매 기능(BuyFeature)으로 얻은 추가 배수 등이 층층이 쌓인다. 이걸 하나의 calculateWin() 메서드 안에 분기로 넣기 시작하면,...
    📁 system
    system
    2026.04.19
  • 슬롯 페이 조건 체크 — Factory + 런타임 타입 해석
    진행 기간: 슬롯 엔진 추상화 작업과 병행 (2025 하반기) 슬롯 게임은 "당첨 판정" 로직이 슬롯 타입(Payline, Way 등)에 따라 근본적으로 다르다. 가로줄 기준으로 판정하는 Payline 슬롯과, 릴 조합 경로 수로 판정하는 Way 슬롯(예: 243웨이, 1024웨이)은 파라미터 구조·반환 구조·내부 알고리즘이 전부 다르다. 이걸 처음엔 하나...
    📁 system
    system
    2026.04.19
  • 13개 로케일 다국어 시스템 — Svelte derived 합성 + 백엔드 캐시 사전 구성
    진행 기간: 2023.08 2024.02 스포츠 베팅 플랫폼의 다국어 시스템을 프론트엔드부터 백엔드 캐시까지 설계·구현했다. 글로벌 대응을 위해 13개 로케일을 지원했고, 스포츠 베팅이라는 도메인 특성상 UI 문구뿐 아니라 경기 마켓 이름, 선수 이름 치환, 핸디캡 표기 같은 템플릿 번역까지 필요했다. --- 일반 웹 서비스의 i18n과는 결이 달랐다....
    📁 system
    system
    2026.04.19
  • 첫 슬롯을 만들며 시작된 1년의 아키텍처 정리 — SpinOperationHandler와 static 해체, 그리고 남은 과제
    진행 기간: 2024.06 2025.11 슬롯팀에 합류해 첫 슬롯(Slot 21 — 클러스터 + 텀블링 + 머지)을 맡으면서 마주친 코드베이스는 테스트를 붙이기가 매우 어려운 상태였다. 작은 단위로 TDD 형태로 접근해보려 했지만, 로직이 강결합되어 있고 스프링 컴포넌트를 static으로 호출하는 구조가 도처에 깔려 있어 곧 벽에 부딪혔다. "이걸 한 번에...
    📁 system
    system
    2026.04.19

댓글 (0)