
Elixir의 Open Telecom Platform(OTP) 원칙에 따라 분산 환경(distributed process)에서 상태를 관리하는 chat 프로세스를 구현
mix new chat --sup
Chat.Application:
Chat.WorkerSupervisor:
Chat.Worker:

애플리케이션 부트
Chat.Application.start/2가 실행되면서 ETS 테이블(Chat.Store)이 생성되고, Chat.WorkerSupervisor가 최상위 Supervisor의 자식으로 시작됩니다.
Worker 생성 및 글로벌 등록
필요할 때 Chat.WorkerSupervisor.start_worker(name)를 호출하여 새로운 워커를 생성합니다.
각 워커는 Chat.Worker.start_link(name)을 통해 GenServer로 시작되며, via(name)을 이용해 글로벌 레지스트리에 등록되어 분산 환경에서도 접근할 수 있습니다.
상태 관리 및 통신
워커는 ETS 테이블에서 이전 상태를 조회하여 초기화하며, inc/2와 value/1 함수를 통해 외부 요청을 처리합니다.
GenServer.cast/2를 사용해 비동기적으로 값을 증가시키고, GenServer.call/2로 동기적으로 현재 값을 조회합니다.
프로세스 종료 및 상태 보존
워커가 종료되면 terminate/2 콜백이 호출되어 현재 상태를 ETS 테이블에 저장합니다. 이를 통해 장애 발생 시에도 상태를 복구할 수 있습니다.
어플리케이션 초기화:
한 터미널에서 애플리케이션(즉, Chat.Application)이 시작
이 때 OTP 애플리케이션이 부팅 -> 전체 시스템의 최상위 트리가 구성
내부 구성 요소 초기화:
Chat.Application은 자식 프로세스들을 시작
@impl true
def start(_type, _args) do
children = [
# Starts a worker by calling: Chat.Worker.start_link(arg)
{Chat.Server, []}, # global chat server
Chat.Supervisor, # dynamic supervisor managing proxy servers
{Chat.ProxyServer, 6666} # for TCP connection
]
...
클라이언트의 접속 및 요청 처리:
여러 터미널의 연결:
이런 방식으로, 여러 터미널(즉, 여러 클라이언트)이 하나의 중앙 채팅 서버(및 관련 OTP 구조)와 연결되어 메시지를 주고받게 되는 시스템을 구현
Chat.Application:
OTP 애플리케이션의 진입점입니다.
애플리케이션 시작 시, Chat.Application이 children 리스트에 따라 Chat.Server, Chat.Supervisor, 그리고 Chat.ProxyServer를 시작합니다.
ETS 테이블(Chat.Store)도 이 시점에 생성됩니다.
Chat.Server:
전역 등록되어 하나의 중앙 채팅 서버로 동작합니다.
클라이언트(또는 프록시 서버)에서 오는 요청을 GenServer.call을 통해 처리합니다.
상태는 ETS를 통해 저장 및 복원됩니다.
Chat.ProxyServer:
OTP 자식으로 등록되어, TCP 리스닝 소켓을 열고 클라이언트의 연결을 기다립니다.
각 클라이언트 연결마다 별도의 프로세스(생성된 Task나 spawn된 프로세스)를 생성하여 클라이언트의 명령어를 처리합니다.
처리한 명령어를 Chat.Server에 전달하고, 결과를 클라이언트에게 응답합니다.
소통 방법:
내부 통신:
Chat.ProxyServer는 클라이언트의 텍스트 명령어를 받아 GenServer.call을 사용해 Chat.Server와 통신합니다.
Chat.Server는 요청을 처리하고 결과를 반환합니다.
외부 통신:
Chat.ProxyServer는 TCP를 통해 클라이언트와 통신합니다. 클라이언트는 TCP 연결을 통해 명령어를 전송하고, 응답을 받습니다.
실행 순서:
Chat.Application이 시작되면서 ETS 테이블이 생성되고, children 리스트에 등록된 Chat.Server, Chat.Supervisor, Chat.ProxyServer가 차례로 실행됩니다.
Chat.ProxyServer는 TCP 포트(기본 6666)에서 연결을 수락하며, 여러 클라이언트 연결을 지속적으로 처리할 수 있는 accept 루프를 실행합니다.
클라이언트가 접속하면 ProxyServer의 각 연결 처리 프로세스가 클라이언트의 명령어를 파싱하여 Chat.Server에 요청하고, 결과를 다시 클라이언트에 전송합니다.
Passive 모드 (active: false) vs Active 모드 (active: true)
모드 특징
Passive - :gen_tcp.recv(socket, 0)을 명시적으로 호출해야 데이터 수신 가능
프록시 서버가 요청을 명확히 제어하기 위해서
데이터를 순차적으로 처리하기 위해서 (active 모드는 무작위로 메시지를 받을 수 있음)
https://en.wikipedia.org/wiki/Open_Telecom_Platform
https://serokell.io/blog/elixir-otp-guide#supervisor
https://hexdocs.pm/elixir/erlang-term-storage.html
https://elixirschool.com/en/lessons/advanced/otp_concurrency
ChatGPT