[channels & daphne] 도입 여부 결정

Saemi An·2025년 5월 17일
post-thumbnail

깃헙에서 Bucky의 Pro Django Tutorial 산출물을 살펴보면 다음과 같은 패키지를 사용하고 있다;

channels = {extras = ["daphne"], version = "^4.0.0"}

이전 포스팅에서 channels는 Django에 WebSocket, HTTP2, 비동기 처리(Async)를 추가하는 프레임워크이며,
daphne는 ASGI 서버로, Django Channels를 실행하는 데 사용된다고 알아본 적이 있다.

우선 포레포레 프젝에 있는 예약구매 시스템을 고려했을 때 이것이 필요할지 고민하고,
필요한 경우 기술에 대한 개념적 이해를 더해보자.


🪼 정말 필요한가?

포레포레 주문 페이지 예시

내 개인 프젝 '포레포레'는 기본적으로 빵을 예약 주문하는 서비스 이기 때문에 위와 같은 페이지가 존재한다.
홀케이크 예약주문의 경우, 날짜를 선택하면 픽업 가능한 시간들 중 원하는 시간을 골라 예약을 진행하는 시스템이다.

이때 고려할 점은 다음과 같다;
1. 실시간성: 유저가 주문서를 작성하는 도중에 다른 유저에 의해 선점된(주문까지 완료된) 시간이 있다면 이를 빠르게 반영하여 노출해야함.
2. 경쟁처리: 동일한 시간대를 여러명이 선택하는 상황에서 충돌 방지
3. 데이터 무결성: 중복 시간 예약 혹은 이로 인한 주문 누락이 발생해서는 안됨
4. 트래픽 관리: 트래픽 폭주시 서버가 감당할 수 있도록 분산처리

🐠 실시간성 | 웹 소켓 필요

웹 소켓은 서버와 클라이언트가 실시간으로 양방향 통신을 유지할 수 있게 한다.
따라서 예약 가능한 시간의 상태를 지속적으로 갱신하고 브로드캐스트(모든 클라이언트에 전달) 해야하는 경우에 매우 적합하다.

즉, 1. 실시간성을 확보할 수 있다.

🐠 데이터 무결성 | 웹 소켓 불필요

하지만 실제 결제 프로세스에는 안정성을 위해 HTTP + 동기처리가 더 적절할 수 있다.
결제 API 호출시 Synchronous HTTP 요청을 날리게 되면 (응답이 오기까지 다른 요청을 처리하지 않기 때문에)트랜젝션이 보장되기 때문이다.

즉, 여기서 DB transaction까지 걸어버리면 3. 데이터 무결성을 확보할 수 있다.

🐠 이외

2. 경쟁처리를 위해 DB Lock, Redis Lock을 사용하고,
4. 트래픽 관리를 위해서는 대기열을 처리하기 위해 Redis Queue 시스템을 활용할 수 있다. (설마 Kafka 까지 필요하지 않겠지..)

🐠 현재 프로젝트 구조에서 요청을 처리하는 방식

클라이언트 요청 -> Nginx(웹서버) -> Gunicorn(애플리케이션 서버=WGSI 서버) -> Django

A. Nginx(웹서버) 역할

  • 클라이언트로부터 요청 수신
  • 정적파일 처리
  • 동적 요청은 구니콘으로 전달

B. Gunicorn(애플리케이션 서버=WGSI 서버)

  • WSGI를 구현한 서버로, Django의 wsgi.py를 로딩하여 앱을 실행
  • 클라이언트 요청을 Nginx로부터 받아 Django로부터 응답을 받아옴

C. Django(백엔드 프레임워크)

  • 구니콘이 실행하는 Python 앱
  • 라우팅, DB 접근, 뷰 렌더링 등을 처리

🐠 구니콘은 어떻게 장고앱을 실행하는가? 다른 기능은?

구니콘 소켓 바인딩시 --bind some/path/to/project_name/wsgi:application 코드를 작성한 기억이 있다. 여기서 구니콘이 장고 앱을 실행시키는구나!

이외에도 구니콘은 다음과 같은 기능을 한다;
1. WSGI 애플리케이션 로딩 → Django 앱 로딩
2. HTTP 요청 수신 → 클라이언트 요청 수신
3. WSGI 방식으로 요청 처리 위임 → Django에 요청 전달
4. 응답 수신 후 전송 → Django 응답을 클라이언트로 전송
5. 프로세스/워커 관리 → 멀티프로세싱, 워커 프로세스 수 관리
6. 에러 핸들링 및 로깅 → 서버 장애, 예외 대응

그렇다면 이제 필요성이 입증 되었으니..
관련 기술에 대한 이해를 해보자!

🪼 WSGI vs ASGI

🐠 WSGI(Web Server Gateway Interface)

  • 파이썬 [웹 애플리케이션 - 웹 서버] 사이의 표준 인터페이스
  • Django의 디폴트 인터페이스이며, HTTP 요청 처리에만 적합하다.
  • 웹소켓 등의 비동기 처리가 어렵다.
  • 클라이언트 요청 처리 과정;
    👩🏻‍💻 -> 애플리케이션 서버이자 uWSGI(gunicorn) -> Django(WSGI) -> Response

참고로 WSGI는 프로토콜이자 규격 이고 (소프트웨어 X),
uWSGI는 WSGI를 구현한 웹 서버 혹은 애플리케이션 서버이다.
(uWSGI는 WSGI뿐만 아니라 HTTP, ASGI 프로토콜 등도 지원하기 때문에 'WSGI를 실행할 수 있는 서버'들 중 하나이다.)

🐠 ASGI(Asynchronous Server Gateway Interface)

  • WSGI의 업그레이드 버전으로, HTTP 뿐만 아니라 WebSocket비동기 프로토콜 처리가 가능하다.
  • Django Channels는 이 ASGI를 활용한다.
  • 클라이언트 요청 처리 과정;
    👩🏻‍💻 -> Daphne(ASGI 서버) -> ASGI 어플리케이션(Django + Channgels)
    • ASGI 어플리케이션(Django + Channgels) - HTTP 요청 -> Django HTTP 핸들러
    • ASGI 어플리케이션(Django + Channgels) - WebSocket 요청 -> Channels consumer
  • 전제 조건:
    • Django 3.0 이상 + channels 패키지 설치
    • agsi.py 파일 작성 및 추가 설정
    • Daphne 등과 같은 ASGI 서버 사용
    • Redis 등 Channel Layer 구성 필수

🐠 Daphne

Daphne는 Django Channels를 지원하기 위해 개발된 HTTP, HTTP2, WS 프로토콜 서버로서, HTTP와 WS 요청을 받아들여 자동으로 어떤 프로토콜을 사용할지 스스로 결정한다. (출처)

🪼 동기 vs 비동기 프로토콜

🐠 동기(Synchronous) vs 비동기(Asynchronous)

동기 통신에서는 순차적으로 일을 처리하며, 앞의 일이 끝날 때까지 기다렸다가 이후의 일처리를 한다.
예를 들어 친구에게 카톡으로 '밥 같이 드실?'이라고 물어보면 친구가 답장을 할 때까지 아무것도 안하고 기다린다. 이후 답장이 오면 약속을 잡는다.

비동기 통신에서는 병렬적 혹은 이벤트 기반으로 일을 처리하며, 요청에 대한 응답을 기다리지 않고 다른 일처리를 한다. 그동안 이전 요청에 대한 응답이 오면 콜백, 이벤트 루프, await 등을 통해 따로 처리한다.
예를 들어 위와 같은 상황에서 친구가 답장이 올 때까지 집안일을 한다더낙 게임을 한다던가 하면서 기다렸다가, 답장이 오면 처리한다.
즉, 티켓 예매와 같은 실시간 처리에는 비동기 통신이 필수적이다!

🐠 동기 통신 예시 - HTTP

애플리케이션 계층 프로토콜인 HTTP Protocol의 요청 - 응답 모델은 일반적으로 동기적인 방식으로 사용된다. 즉, 클라이언트가 요청을 보내면 서버에서 응답이 올 때까지 기다린다.

하지만 Ajax나 Fetch 함수를 통해 HTTP 요청을 비동기적으로 처리할 수 있다.

(즉, HTTP 프로토콜이 본질적으로 동기/비동기성을 내포하는 프로토콜은 아니다. 이는 프로그래밍 방식에 따라 달라질 수 있다.)

🐠 + α) Stateless HTTP/1.1

HTTP 통신의 무상태성(Stateless)은 이전 요청에 대한 '상태'를 서버가 기억하지 않는다는 뜻. 즉, 클라이언트가 서버에 요청을 보낼 때, 매번 필요한 모든 정보(인증 정보, 세션 ID, 쿠키 등)를 함께 보내야함. 서버는 이전 요청에 대한 기억이 없기 때문에.

🐠 + α) Keep-Alive HTTP/1.1

HTTP/1.1에서는 TCP 연결을 여러 HTTP 요청/응답 간에 재사용하여 연결을 유지(persistant)하는 것이 기본값이다.
(HTTP/1.0에서는 기본적으로 매 요청마다 연결을 끊었음)

하지만 단순히 '물리적 연결(TCP 소켓)'만 유지한다는 것이지, '논리적 상태(사용자 로그인 정보를 포함하는 쿠키 및 세션 등)'를 유지한다는 뜻은 아니다.

예를 들어 클라이언트가 서버에 로그인 요청을 보냈고, 서버가 로그인 성공을 응답한 이후,
클라이언트가 다시 다른 요청을 보낸다면 서버는 이 요청이 어떤 사용자로부터 온 건지 기억하지 못한다. 따라서 클라이언트는 매번 쿠키나 토큰 등을 요청에 포함시켜야 한다.

즉, 이 두 요청은 같은 TCP 연결(keep-alive)로 보내졌다고 해도, 서버는 여전히 stateless하게 동작한다.

🪼 웹 소켓

WebSocket은 클라이언트(브라우저)와 서버가 "끊기지 않는 하나의 연결"을 유지하면서, 서로 자유롭게 데이터를 주고받을 수 있는 통신 프로토콜이다. 다음과 같은 특징이 있다;

  1. 양방향성 - 클라이언트가 서버로 메세지를 보내고, 서버도 클라이언트에 즉시 푸시 가능
  2. 지속 연결 - 한 번 연결되면 계속 사용함(http/1.1에서 keep-alive 특징은 '연결을 재사용' 하는 개념. Websocket은 '연결 자체를 양방향 스트림 방식'으로 바꾸는 것.)
  3. 실시간성 - 지연 없이 즉시 통신 가능(실시간 채팅, 게임, 주식, 시세 등에 적합)
  4. 경량 헤더 - HTTP보다 훨씬 가벼움

🪼 Redis

Redis(Remote Dictionary Server)란 오픈 소스의 인메모리 데이터 저장소이다. Key-Value 기반의 초고속 NoSQL 데이터 베이스!

  • pub/sub 기능 지원 (→ Channels에서 이걸 이용해 WebSocket 메시지를 라우팅함)
  • TTL (유효기간) 지원 → 캐시로도 자주 사용됨

Django Channels에서 웹소켓 요청 처리시 다양한 사용자 간의 메세지를 브로드캐스팅 하거나 큐잉 해야한다. 이때 Redis는 Channel Layer라는 메세지 중계 역할을 한다. 즉, 중간 통신 허브 같은 느낌!


디버깅을 하며 느낀점은
코드 실행 프로세스를 모르면 디버깅을 할 수 없다는 것이다.

하지만 프로세스를 알기 위해서는 내가 가져다 쓴 기술에 대한 지식이 있어야 한다.

하지만 그 지식은 너무나도 방대하니..

일단 기술을 사용해 보면서 익숙해지고
그 과정에서 간략하게나마 기술에 대한 지식을 습득하고

이 동작을 반복해 가면서 손가락이 숙달 되도록 하자.

그런 의미에서 다음편에는 이 유툽 튜토리얼을 따라해 보고자 한다.

끝!

profile
하나씩 차근차근 천천히

0개의 댓글