플러팅 AI/Flask Server

다중 워커 간 메모리 공유 문제 해결

Solo.dev 2024. 12. 3. 14:05

https://talk7053.tistory.com/13

 

다중 워커 간 메모리 공유 문제 이해

문제 1: 다중 워커 간 connected_clients 상태 불일치현상: WebSocket 클라이언트가 연결된 상태인데도 특정 워커에서 connected_clients에 해당 클라이언트 ID (request.sid)가 없다고 판단합니다.원인:Gunicorn의

talk7053.tistory.com

다중 워커 간 메모리 공유 문제 해결 방법

문제 개요

  • 웹소켓과 HTTP를 사용하는 애플리케이션에서 다중 워커를 사용하는 경우, WebSocket 연결을 처리한 워커HTTP 요청을 처리하는 워커가 달라질 수 있음.
  • **전역 변수 connected_clients**를 사용해 클라이언트의 sid를 관리하는 구조에서는, 요청을 처리하는 워커가 달라지면 sid를 찾지 못해 통신 문제가 발생.

해결 방법

1. Redis 도구 사용하기

Redis를 활용하여 전역 상태를 중앙 집중식으로 관리하면, 모든 워커가 동일한 상태를 공유할 수 있습니다.

구현 방식

  1. Redis 설치 및 연결:
    • 애플리케이션 서버와 Redis 서버를 연결하여 상태를 저장하고 조회합니다.
    • Google Cloud Memorystore(Redis) 또는 자체 Redis 서버를 사용할 수 있습니다.
  2. WebSocket 연결 정보 Redis에 저장:
    • 클라이언트가 WebSocket에 연결되면 sid와 연결된 정보를 Redis에 저장.
    • 예:

      import redis
      redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

      # WebSocket 연결 시 저장
      def on_connect(sid):
          redis_client.hset("connected_clients", sid, "worker_id_A")
  3. HTTP 요청 시 Redis에서 조회:
    • HTTP 요청이 들어오면 sid를 Redis에서 조회하여 적절한 상태를 가져옴.
    • 예:
      # HTTP 요청 시 조회
      def handle_http_request(sid):
          worker_id = redis_client.hget("connected_clients", sid)
          if worker_id:
              # 해당 워커로 요청 전달
              forward_request_to_worker(worker_id)
  4. Redis를 통한 상태 공유:
    • 모든 워커가 동일한 Redis 인스턴스를 참조하므로, 상태 공유 문제가 발생하지 않음.

장점

  • 확장성: 워커 수가 증가해도 Redis를 통해 상태를 쉽게 공유 가능.
  • 유연성: WebSocket 외에도 다른 상태(예: 작업 큐, 세션 정보) 관리에 Redis를 활용 가능.
  • 장애 복구: 워커 장애 시 Redis에서 상태를 조회하여 복구 가능.

단점

  • 추가 비용: Redis 호스팅 비용 발생.
  • 복잡성 증가: Redis 설정 및 관리 필요.
  • 약간의 성능 오버헤드: 네트워크를 통한 상태 조회로 인해 약간의 지연이 있을 수 있음.

2. Google Cloud Load Balancer로 세션 고정하기

Google Cloud Load Balancer(GCLB)의 Sticky Sessions를 활용하여, 클라이언트 요청(WebSocket 및 HTTP 요청)이 항상 동일한 워커로 라우팅되도록 설정합니다.

구현 방식

  1. GCLB 생성 및 설정:
    • GCP 콘솔에서 HTTP(S) Load Balancer를 생성.
    • 로드 밸런서의 Backend 서비스를 Cloud Run, Compute Engine, GKE 등으로 연결.
  2. Session Affinity 활성화:
    • Backend 서비스의 설정에서 Session Affinity를 활성화.
    • 세션 고정 방식 선택:
      • Generated Cookie: 로드 밸런서가 클라이언트 쿠키를 생성하여 동일한 워커로 라우팅.
      • Client IP: 클라이언트의 IP 주소를 기준으로 동일 워커로 고정.
  3. WebSocket 및 HTTP 요청의 워커 일치:
    • 클라이언트가 WebSocket 연결을 설정한 후 HTTP 요청을 보낼 때, GCLB가 항상 동일 워커로 요청을 라우팅.

장점

  • 간단한 설정: 클라이언트 세션 고정을 위해 애플리케이션 레벨의 추가 코드가 필요 없음.
  • 전역 상태 관리 불필요: 모든 상태가 동일 워커에서 유지되므로 Redis와 같은 중앙 상태 저장소가 필요 없음.
  • 성능 최적화: 네트워크를 통한 상태 조회가 없으므로 응답 속도가 빠름.

단점

  • 부하 분산 제한: 클라이언트 세션이 특정 워커에 고정되므로, 워커 간 부하가 비대칭적일 수 있음.
  • 장애 복구 한계: 특정 워커가 다운되면 해당 세션이 끊어질 수 있음.
  • 로드 밸런서 비용: GCLB 사용 시 시간 기반 및 데이터 전송량 기반 비용 발생.

비교

항목Redis 도구 사용Google Cloud Load Balancer 사용

상태 관리 방식 중앙 집중식(Redis를 통한 상태 공유) 클라이언트 요청을 동일 워커로 라우팅하여 상태 일관성 유지
구현 복잡성 상대적으로 복잡 (Redis 설정 및 상태 동기화 필요) 상대적으로 간단 (GCLB 설정만 필요)
확장성 매우 높음 세션 고정으로 부하 분산이 제한될 수 있음
장애 복구 Redis에서 상태 조회 가능 워커 장애 시 세션이 끊길 수 있음
추가 비용 Redis 사용 비용 발생 GCLB 비용 발생
응답 속도 Redis 조회로 인해 약간의 지연 네트워크 요청 없이 빠름
적합한 애플리케이션 크기 대규모 클라이언트 및 다수 워커 중소규모 클라이언트 및 적은 워커 수

 

비용 비교

항목Redis 사용Google Cloud Load Balancer 사용

RedisVS cloud load Balancer Redis cloud load Balancer
24시간 인스턴스 비용 $1.14 $0.36
데이터 전송 비용 (10GB) $1.20 $0.25
요청 처리 비용 (1만번) 없음 $0.00008
총 비용 (1일) $2.34 $0.61