GC와 컨테이너 환경에서의 메모리 관리

GC와 컨테이너 환경에서의 메모리 관리

컨테이너 기술은 애플리케이션의 배포와 스케일링을 간소화하여 개발자들에게 많은 이점을 제공합니다. 그러나 컨테이너 환경에서는 리소스가 제한적이기 때문에 메모리 관리와 Garbage Collection(GC)에 대한 이해와 최적화가 더욱 중요합니다. 이번 글에서는 컨테이너 환경에서의 GC 동작 방식과 메모리 관리 전략에 대해 알아보겠습니다.

컨테이너 환경의 특징

리소스 제한

컨테이너는 호스트 시스템의 커널을 공유하면서 격리된 환경을 제공합니다. 각 컨테이너는 CPU, 메모리 등의 리소스 제한을 설정할 수 있으며, 이러한 제한은 컨테이너 내부의 애플리케이션에 직접적인 영향을 미칩니다.

메모리 제한과 OOM Killer

컨테이너는 메모리 사용량에 제한을 둘 수 있으며, 이 제한을 초과하면 Out Of Memory(OOM) Killer에 의해 프로세스가 강제로 종료될 수 있습니다. 이는 애플리케이션의 안정성에 큰 영향을 미칩니다.

컨테이너에서의 GC 동작 방식

기본 설정의 문제점

기본적으로 .NET의 GC는 호스트 시스템의 전체 메모리를 기준으로 동작합니다. 컨테이너에서 메모리 제한을 설정하더라도, GC는 이를 인식하지 못하고 메모리를 과도하게 사용할 수 있습니다.

COMPlus 환경 변수를 통한 GC 설정

컨테이너 환경에서 GC의 동작을 조정하기 위해 COMPlus 환경 변수를 사용할 수 있습니다.

  • COMPlus_GCHeapHardLimit: 힙 메모리의 최대 크기를 제한합니다.
  • COMPlus_GCHeapHardLimitPercent: 사용 가능한 메모리의 백분율로 힙 메모리 크기를 설정합니다. 예시:
ENV COMPlus_GCHeapHardLimit=2147483648 # 힙 메모리 최대 크기를 2GB로 설정

DOTNET_ 환경 변수 사용

.NET Core 3.0 이상에서는 DOTNET_ 접두사가 있는 환경 변수를 사용하여 GC 설정을 조정할 수 있습니다.

  • DOTNET_GCHeapHardLimit: 힙 메모리의 하드 리밋을 설정합니다.
  • DOTNET_GCHeapHardLimitPercent: 총 메모리의 백분율로 힙 메모리 크기를 설정합니다. 예시:
ENV DOTNET_GCHeapHardLimit=2147483648 # 힙 메모리 최대 크기를 2GB로 설정

컨테이너 메모리 제한 인식

.NET 5.0 이상에서는 컨테이너의 메모리 제한을 자동으로 인식하여 GC가 동작합니다. 그러나 이전 버전에서는 수동으로 설정해야 합니다.

메모리 관리 전략

메모리 사용량 모니터링

컨테이너 내에서 메모리 사용량을 지속적으로 모니터링하여 메모리 누수나 과도한 메모리 사용을 감지합니다. 예시:

using System.Diagnostics;
long memoryUsage = Process.GetCurrentProcess().WorkingSet64;
Console.WriteLine($"메모리 사용량: {memoryUsage / (1024 * 1024)} MB");

GC 모드 조정

컨테이너 환경에서는 서버 GC(Server GC) 모드를 사용하는 것이 일반적입니다. 그러나 리소스 제한이 있는 환경에서는 워크스테이션 GC(Workstation GC) 모드가 더 적합할 수 있습니다. 서버 GC 비활성화 예시:

ENV DOTNET_GCServer=0

CPU 코어 수 제한

GC는 CPU 코어 수에 따라 동작이 달라집니다. 컨테이너에서 코어 수를 제한하면 GC가 이를 인식하고 적절히 동작합니다. Docker 실행 시 코어 수 제한 예시:

docker run --cpus=2 myapp

메모리 할당 최소화

컨테이너의 제한된 메모리 내에서 효율적으로 동작하려면 메모리 할당을 최소화해야 합니다.

  • 불필요한 객체 생성 방지
  • 객체 풀링 사용
  • Span<T>Memory<T> 활용

메모리 릭 방지

메모리 누수는 컨테이너 환경에서 더욱 치명적입니다. 철저한 메모리 관리로 메모리 누수를 방지해야 합니다.

  • IDisposable 구현 객체의 적절한 해제
  • 이벤트 핸들러의 구독 해제
  • 약한 참조(Weak Reference) 사용

컨테이너 환경에 맞는 GC 설정 사례

예제 1: 메모리 제한이 있는 컨테이너에서의 설정

메모리 제한이 512MB인 컨테이너에서 애플리케이션을 실행하는 경우:

ENV DOTNET_GCHeapHardLimitPercent=75
ENV DOTNET_GCServer=0
  • 힙 메모리 크기를 전체 메모리의 75%로 제한
  • 워크스테이션 GC 모드 사용

예제 2: 멀티코어와 대용량 메모리를 사용하는 컨테이너

고성능이 요구되는 애플리케이션에서:

ENV DOTNET_GCHeapHardLimit=4294967296 # 4GB
ENV DOTNET_GCServer=1
  • 힙 메모리 크기를 4GB로 제한
  • 서버 GC 모드 사용

Kubernetes에서의 메모리 관리

리소스 리퀘스트와 리밋 설정

Kubernetes에서 컨테이너의 리소스 사용을 제어하기 위해 리소스 요청과 제한을 설정합니다. 예시:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
    - name: myapp-container
      image: myapp:latest
      resources:
        requests:
          memory: "512Mi"
          cpu: "500m"
        limits:
          memory: "1Gi"
          cpu: "1"

Downward API를 통한 메모리 제한 인식

애플리케이션 내부에서 Kubernetes의 메모리 제한을 인식하기 위해 Downward API를 활용합니다. 예시:

env:
  - name: CONTAINER_MEMORY_LIMIT
    valueFrom:
      resourceFieldRef:
        resource: limits.memory

C# 코드에서 환경 변수를 읽어 설정에 활용합니다.

string memoryLimitStr = Environment.GetEnvironmentVariable("CONTAINER_MEMORY_LIMIT");
// 메모리 제한 값 파싱 및 적용

결론

컨테이너 환경에서는 리소스가 제한적이므로 GC와 메모리 관리에 대한 세심한 설정이 필요합니다. 적절한 GC 설정과 메모리 관리 전략을 통해 애플리케이션의 안정성과 성능을 향상시킬 수 있습니다. 컨테이너의 특성을 고려한 메모리 최적화로 효율적인 애플리케이션을 개발하시기 바랍니다.