
깃헙에서 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 서버)
C. Django(백엔드 프레임워크)


구니콘 소켓 바인딩시 --bind some/path/to/project_name/wsgi:application 코드를 작성한 기억이 있다. 여기서 구니콘이 장고 앱을 실행시키는구나!
이외에도 구니콘은 다음과 같은 기능을 한다;
1. WSGI 애플리케이션 로딩 → Django 앱 로딩
2. HTTP 요청 수신 → 클라이언트 요청 수신
3. WSGI 방식으로 요청 처리 위임 → Django에 요청 전달
4. 응답 수신 후 전송 → Django 응답을 클라이언트로 전송
5. 프로세스/워커 관리 → 멀티프로세싱, 워커 프로세스 수 관리
6. 에러 핸들링 및 로깅 → 서버 장애, 예외 대응


그렇다면 이제 필요성이 입증 되었으니..
관련 기술에 대한 이해를 해보자!
참고로 WSGI는 프로토콜이자 규격 이고 (소프트웨어 X),
uWSGI는 WSGI를 구현한 웹 서버 혹은 애플리케이션 서버이다.
(uWSGI는 WSGI뿐만 아니라 HTTP, ASGI 프로토콜 등도 지원하기 때문에 'WSGI를 실행할 수 있는 서버'들 중 하나이다.)
Daphne는 Django Channels를 지원하기 위해 개발된 HTTP, HTTP2, WS 프로토콜 서버로서, HTTP와 WS 요청을 받아들여 자동으로 어떤 프로토콜을 사용할지 스스로 결정한다. (출처)
동기 통신에서는 순차적으로 일을 처리하며, 앞의 일이 끝날 때까지 기다렸다가 이후의 일처리를 한다.
예를 들어 친구에게 카톡으로 '밥 같이 드실?'이라고 물어보면 친구가 답장을 할 때까지 아무것도 안하고 기다린다. 이후 답장이 오면 약속을 잡는다.
비동기 통신에서는 병렬적 혹은 이벤트 기반으로 일을 처리하며, 요청에 대한 응답을 기다리지 않고 다른 일처리를 한다. 그동안 이전 요청에 대한 응답이 오면 콜백, 이벤트 루프, await 등을 통해 따로 처리한다.
예를 들어 위와 같은 상황에서 친구가 답장이 올 때까지 집안일을 한다더낙 게임을 한다던가 하면서 기다렸다가, 답장이 오면 처리한다.
즉, 티켓 예매와 같은 실시간 처리에는 비동기 통신이 필수적이다!

애플리케이션 계층 프로토콜인 HTTP Protocol의 요청 - 응답 모델은 일반적으로 동기적인 방식으로 사용된다. 즉, 클라이언트가 요청을 보내면 서버에서 응답이 올 때까지 기다린다.
하지만 Ajax나 Fetch 함수를 통해 HTTP 요청을 비동기적으로 처리할 수 있다.
(즉, HTTP 프로토콜이 본질적으로 동기/비동기성을 내포하는 프로토콜은 아니다. 이는 프로그래밍 방식에 따라 달라질 수 있다.)
HTTP 통신의 무상태성(Stateless)은 이전 요청에 대한 '상태'를 서버가 기억하지 않는다는 뜻. 즉, 클라이언트가 서버에 요청을 보낼 때, 매번 필요한 모든 정보(인증 정보, 세션 ID, 쿠키 등)를 함께 보내야함. 서버는 이전 요청에 대한 기억이 없기 때문에.
HTTP/1.1에서는 TCP 연결을 여러 HTTP 요청/응답 간에 재사용하여 연결을 유지(persistant)하는 것이 기본값이다.
(HTTP/1.0에서는 기본적으로 매 요청마다 연결을 끊었음)
하지만 단순히 '물리적 연결(TCP 소켓)'만 유지한다는 것이지, '논리적 상태(사용자 로그인 정보를 포함하는 쿠키 및 세션 등)'를 유지한다는 뜻은 아니다.
예를 들어 클라이언트가 서버에 로그인 요청을 보냈고, 서버가 로그인 성공을 응답한 이후,
클라이언트가 다시 다른 요청을 보낸다면 서버는 이 요청이 어떤 사용자로부터 온 건지 기억하지 못한다. 따라서 클라이언트는 매번 쿠키나 토큰 등을 요청에 포함시켜야 한다.
즉, 이 두 요청은 같은 TCP 연결(keep-alive)로 보내졌다고 해도, 서버는 여전히 stateless하게 동작한다.
WebSocket은 클라이언트(브라우저)와 서버가 "끊기지 않는 하나의 연결"을 유지하면서, 서로 자유롭게 데이터를 주고받을 수 있는 통신 프로토콜이다. 다음과 같은 특징이 있다;

Redis(Remote Dictionary Server)란 오픈 소스의 인메모리 데이터 저장소이다. Key-Value 기반의 초고속 NoSQL 데이터 베이스!
Django Channels에서 웹소켓 요청 처리시 다양한 사용자 간의 메세지를 브로드캐스팅 하거나 큐잉 해야한다. 이때 Redis는 Channel Layer라는 메세지 중계 역할을 한다. 즉, 중간 통신 허브 같은 느낌!
디버깅을 하며 느낀점은
코드 실행 프로세스를 모르면 디버깅을 할 수 없다는 것이다.
하지만 프로세스를 알기 위해서는 내가 가져다 쓴 기술에 대한 지식이 있어야 한다.
하지만 그 지식은 너무나도 방대하니..
일단 기술을 사용해 보면서 익숙해지고
그 과정에서 간략하게나마 기술에 대한 지식을 습득하고
이 동작을 반복해 가면서 손가락이 숙달 되도록 하자.
그런 의미에서 다음편에는 이 유툽 튜토리얼을 따라해 보고자 한다.
끝!