데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Redis sub/pub 를 통한 scale out에 유연한 서버 간 통신 구조 만들기

      jay189 25.07.03
      1,969 5 2
      DEVOTEE 요약
      본 블로그는 기존 SSH 기반 서버-인스턴스 통신의 병목 문제를 해결하기 위해 "Redis Pub/Sub 구조"를 활용한 비동기 통신 방식 도입을 논의한 내용입니다. Redis Pub/Sub은 간단한 구조와 빠른 메시지 전달이 강점으로, 발행자와 구독자 간의 유연한 비동기 통신을 가능하게 합니다. 작성자는 Redis Pub/Sub을 활용해 작업 큐와 결과 처리를 구현하는 Python 코드를 예제로 소개하며, 이 방식을 통해 확장 가능하고 유지 보수가 용이한 시스템을 제안했습니다.
      DEVOTEE 추천 블로그

      들어가며

      안녕하세요. SK Hynix의 AI Transformation 팀 김지수입니다.


      이번에 업무를 진행하면서 redis sub / pub 구조를 고민하게 된 과정 대해 공유 차 작성하게 되었습니다.

      비슷한 고민이 있으시다면 함께 나눠보면 좋을 것 같네요.


      문제 정의

      우선 서버-인스턴스 간 통신을 통해 특정 작업을 진행하는 과정이 있습니다.


      기존엔 SSH를 통해 인스턴스에서 작업을 지시하는 방식으로 구현되었으나 대량의 작업이 필요할 경우,

      서비스의 고도화를 위해 다수의 클라이언트 서버와 통신해야 하는 과정에서 병목이 발생하였습니다.


      이해를 돕기 위해 간단하게 도식화 해보면 다음과 같습니다.

      image.png

      자 그럼 이 문제를 해결하기 위해 어떤 고려 사항이 있을지 정리해보면..

      • 기존 시스템에 빠르게 변경 가능한 구조

      • 유연한 scale out 구조로 전환

      • 로깅 & 유지 보수 용이

      • 확장 가능한 모듈 형태

      • Latency가 중요하진 않음

      • client, worker, pod의 개수가 가변적

      • Redis DB

      정도가 있겠네요


      대표적인 통신 프레임워크 정리

      다음으로는 병목의 주 원인이었던 SSH 구조를 대체할 만한 프레임워크를 프롬프트를 통해 정리해 보았습니다.

      라이브러리/프레임워크

      주요 특징

      장점

      단점

      gRPC

      구글에서 개발한 고성능 RPC 프레임워크, Protocol Buffers 기반

      - 빠르고 효율적인 바이너리 전송

      - 다양한 언어 지원

      - 양방향 스트리밍 지원

      - 자동 코드 생성

      - REST에 비해 진입장벽 높음

      - 디버깅/모니터링 어려움

      - 브라우저 직접 지원 제한적

      RESTful API (Flask, Express 등)

      HTTP 기반의 표준 API 통신 방식

      - 쉬운 구현과 디버깅

      - 브라우저 및 다양한 클라이언트 지원

      - 인프라 친화적

      - 텍스트 기반(성능 한계)

      - 스키마 강제성 약함

      - 실시간/양방향 통신에 부적합

      RabbitMQ (AMQP)

      메시지 브로커, 비동기 메시지 큐

      - 신뢰성 높은 메시지 전달

      - 다양한 패턴(큐, pub/sub 등) 지원

      - 확장성 우수

      - 운영 복잡성

      - 실시간성은 다소 떨어짐

      - 추가 인프라 필요

      Redis Pub/Sub

      인메모리 데이터베이스의 pub/sub 기능

      - 빠른 메시지 전달

      - 간단한 구조

      - 다양한 언어 지원

      - 메시지 유실 가능성(구독 중단 시)

      - 메시지 영속성 없음

      - 복잡한 라우팅/확장성 한계

      저희는 Latency가 크게 중요하지 않으며 구성 서버들이 가변적이므로 비동기 방식을 항상 channel을 subscribe 하는 worker를 둔다면 로깅 문제도 해결할 수 있겠네요.

      표로 보았을 때, redis Pub/Sub 구조가 가장 적당해 보입니다.


      Redis Publish & Subscribe

      그럼 pub / sub 구조로 구현하는 것으로 하고 한 번 자세한 개념을 알아보죠.


      Redis Pub/Sub은 Redis의 기능을 활용한 메시지 브로커 시스템의 일종입니다.

      이 시스템은 데이터를 '발행자'(publisher)가 특정 '채널' 또는 '주제'(topic)에 메시지를 보내면,

      이 채널을 구독하고 있는 '구독자'(subscriber)들이 메시지를 받아 처리할 수 있도록 해주는 통신 방식입니다.

      유튜브 채널 구독과 비슷하게, 크리에이터가 새 글을 발행하면 구독자에게 알림이 오는 원리와 유사하다고 설명될 수 있습니다.


      아하~ 위 예를 보면 인스턴스(publisher)가 무수히 많이 늘어나더라도 간단하게 서버와 인스턴스 간 Pub / Sub 으로 비동기 통신을 구현할 수 있겠네요. 아래 그림처럼 말이죠.

      image.png

      cursor로 Mockup code도 요청해 보았습니다.

      server

      import redis
      import time
      
      REDIS_HOST = 'localhost'
      TASK_QUEUE = 'task_queue'
      RESULT_CHANNEL = 'task_result'
      
      def main():
          r = redis.Redis(host=REDIS_HOST, port=6379, db=0)
          pubsub = r.pubsub()
          pubsub.subscribe(RESULT_CHANNEL)
      
          # 작업 큐에 작업 넣기
          for i in range(5):
              task = f"do_something:{i}"
              r.rpush(TASK_QUEUE, task)
              print(f"[SERVER] Queued: {task}")
      
          print("[SERVER] Waiting for results...")
          received = 0
          while received < 5:
              message = pubsub.get_message()
              if message and message['type'] == 'message':
                  print(f"[SERVER] Result received: {message['data'].decode()}")
                  received += 1
              time.sleep(0.1)
      
      if __name__ == "__main__":
          main()

      instance

      import redis
      import time
      
      REDIS_HOST = 'localhost'
      TASK_QUEUE = 'task_queue'
      RESULT_CHANNEL = 'task_result'
      
      def main():
          r = redis.Redis(host=REDIS_HOST, port=6379, db=0)
          print("[WORKER] Waiting for tasks...")
          while True:
              task = r.blpop(TASK_QUEUE, timeout=0)
              if task:
                  task_data = task[1].decode()
                  print(f"[WORKER] Processing: {task_data}")
                  # 실제 작업 처리 (예시로 2초 sleep)
                  time.sleep(2)
                  result = f"{task_data} done"
                  r.publish(RESULT_CHANNEL, result)
                  print(f"[WORKER] Published result: {result}")
      
      if __name__ == "__main__":
          main()

      실제 구현은 좀 다르게 되겠지만 의도대로 동작하는 코드가 만들어졌습니다.


      읽어주셔서 감사합니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      jay189 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기