2. 소개
프로그래머
2017 달빛 조각사 (C#,Elixir)
2013 문명 온라인 (C++)
2008 더데이 온라인 (C#,C++)
2007 마비노기2 (C#,C++)
2004 SUN 온라인 (C++)
2002 묵향 온라인 (C++)
2002 디지몬 카드 배틀 for PC (C,C++)
2002 건담 G-Generation for PC (C,C++)
3. Elixir 엘릭서
신생 언어 (2011) - José Valim
생소한 언어
함수형 언어
어느 것 하나 실무, 업계에서 접하기 힘든...
사실 저도 달빛조각사 팀에 합류하기 전까지 잘 몰랐습니다.
19. 통합 빌드 툴 mix
프로젝트 생성
빌드
실행
의존성 관리
배포
유닛테스트
$ mix new hello_world
$ mix compile
$ mix run
$ mix deps.get [clean, update, compile]
$ mix release
$ mix test
20. 엄브렐라 프로젝트 umbrella
• 다양한 하위(서브) 프로젝트들을 관리
• visual studio 솔루션과 프로젝트의 관계와 비슷
q5 엄브렐라 프로젝트
- common 하위 프로젝트
- protocol
- world_server
- login_server
- command_center
21. 인클루드 없다.
하위 프로젝트 간 참조 설정만으로 접근하고 함수 호출!
# mix.exs
def deps do
{`common`, in_umbrella: true}
end
# login.ex
defmoule Login do
def process_login(id, pw) do
… 중략 …
Common.Login.authenticate(id, pw)
end
end
22. 파이프 연산자 pipe
코드를 줄여주기도 하지만
코드를 좀 더 읽기 쉽게 해준다.
# a.txt
1,a1,b1,c1
2,a2,b2,c2
# 중첩된 함수 호출은 읽기 어렵다
list = String.split(String.split(File.read(“a.txt”), “n”), “,”)
[1, a1, b1, c1, 2, a2, b2, c2]
# 파이프 연산자
list = File.read(“a.txt”) |> String.split(“n”) |> String.split(“,”)
[1, a1, b1, c1, 2, a2, b2, c2]
23. 패턴 매칭 pattern matching
위 - 아래
읽어가는 순서로 매칭
에러 처리를 쉽게 도와줌
with :ok <- can_authenticate(user_id, user_pw),
:ok <- can_allow_ip(ip),
{:ok, user} <- load_user(user_id) do
put_in(state.user, user)
else
{:error_login_invalid, reason} ->
send_login_error(state.socket, ...)
{:error_login_depulicated, reason} ->
send_login_error(state.socket, ...)
{:error_allow_ip, reason} ->
send_login_error(state.socket, ...)
{:db_error, reason} ->
send_login_error(state.socket, ...)
end
24. 자동 코드 포맷팅
컨벤션 강제
동일한 코드 스타일로 가독성 증가
불 필요한 논쟁에서 벗어남!
아... 이 코드 짠
사람 얼굴을
보고 싶다.
25. 컴파일 속도가 빠르다
Erlang VM위에 동작하는 bytecode로 빌드
현재 달빛 조각사 약 11 만줄 컴파일 20초 내외 끝
29. 프로세스 모델
액터 기반 모델
프로세스 간 메시지 통신
process = actor
mailbox
30. 메시지를 주고 받는 간단한 서버
defmodule TcpServer do
def start(port) do
{:ok, socket} = :gen_tcp.listen(port, [:binary, packet: 0, active: false, reuseaddr: true])
loop_accept(socket)
end
defp loop_accept(socket) do
{:ok, conn} = :gen_tcp.accept(socket)
spawn_link(fn -> recv(conn) end)
loop_listen(socket)
end
defp loop_recv(conn) do
case :gen_tcp.recv(conn, 0) do
{:ok, data} ->
:gen_tcp.send(conn, data)
loop_recv(conn)
{:error, :closed} ->
:ok
end
end
end
32. 가장 무서워 하는 단어 down
항상 서비스 직전의 마음가짐은 조마조마
D-Day가 다가올 수 도록 소화가 잘 안됨
서버 다운됐어요!
한번 살펴 볼께요
서버 프로그래머가
33. Let it Crash!
비즈니스 로직은 언제나 문제가 생길 수 있음
문제가 있으니 죽은 것!
죽었으니 살리면 되지?
장애 복원하는 감시자 supervisor
https://learnyousomeerlang.com/supervisors
문명 온라인 AI 서버에서 효과 봄
34. 깨끗하게 재시작
빈도수가 극히 드문 복잡한 예외 처리
➔ 복잡성 증가가 성능 하락
➔ 유지 보수 어려움
1. 길드원인 캐릭터가 캐릭터 선택하여 인게임으로 진입
2. 길드장이 길드를 파괴
3. 길드원이 인게임 진입하면서 길드 프로세스에 접근하려고 함
4. 길드원 프로세스 크래시 후 재시작
만약 크래시가 나지 않도록 했다면 클라이언트는 이미 길드 정보를 받았지만 길드는 없는 상태
데이터의 무결성이 깨진 상황
35. 클라가
멈췄는데요?
이제는 안심할 수 있다.
와이 파이가
끊어진 거
아니예요?
서버 다운됐어요!
자네만
끊어졌네요.
1주일 에 여러 번 팀 테스트
36. ETS Erlang Term Storage
얼랭 OTP 내장 메모리 캐시 시스템
튜플 데이터를 테이블에 저장
Constant time 에 가져올 수 있는 매우 빠른 메모리 저장 구조
생성한 프로세스와 Life Cycle 동일
> :ets.new(:user, [:public, :named_table])
> :ets.insert(:user, {“유저1”, user_value, …})
> :ets.lookup(:user, “유저1”)
37. 유닛 테스트
코드 상에 샘플 코드를 작성한 후 유닛테스트로 활용
@doc """
add new timer.
## Examples
iex> state = %{ timers: [], elapsed: 0 }
iex> state = ExTimer.add(state, {:handler, :name, "uhaha"}, 2000)
iex> [timer] = state.timers
iex> timer.msg == {:handler, :name, "uhaha"}
true
iex> timer.delay == 2000/1000
true
"""
@spec add(state, tuple | atom, integer) :: state
def add(state, msg, delay) when is_tuple(msg) or is_atom(msg) do
38. 문서화
코드 함수 타입 스펙 정의
여러 마리 토끼 잡기 - 컴파일 에러 + 정적 분석 (dialyzer) + 문서화
@doc """
add new timer.
## Examples
iex> state = %{ timers: [], elapsed: 0 }
iex> state = ExTimer.add(state, {:handler, :
iex> [timer] = state.timers
iex> timer.msg == {:handler, :name, "uhaha"}
"""
@spec add(state, tuple | atom, integer) :: state
def add(state, msg, delay) when is_tuple(msg) or is_atom(msg) do
39. 러닝 커브
생각 보다 학습 난이도가 낮다 (개인적인 생각일지도)
• 함수형 언어
• 매크로
• 얼랭
매크로
얼랭
41. 프로세스를 나누는 기준
프로세스간 인터렉션이 많지 않는 로직
명확하게 분리 가능한 콘텐츠
비동기로 동작하는 로직
메시지 전달 비용 로직 처리 비용
42. 유저 프로세스
접속된 수백 ~ 수천의 클라이언트 : 프로세스 = 1:1
대부분의 클라이언트 요청 패킷은 인터렉션이 없는 액션들
일부 클라이언트의 crash 가 전체 서비스에 영향을 주지 않음
유저(캐릭터) 프로세스클라이언트
TCP 소켓 통신
43. 몬스터 그룹 프로세스
몬스터 AI 담당
플레이어와 대부분 동일한 로직으로 동작
지역별로 몬스터 그룹으로 관리
유저와 인접한 몬스터들과의 메시지 인터렉션 수 줄임
44. 프로세스를 찾는 디렉토리 서비스
콘텐츠 별 많고 다양한 프로세스가 존재
콘텐츠 ID 로 pid (process id) + 캐시된 정보를 찾는 메커니즘이 필요
char_id ➔ 유저 프로세스
chan_id ➔ 채널 프로세스
guild_id ➔ 길드 프로세스
party_id ➔ 파티 프로세스
user_pid = UserDirectory.find(char_id)
alive? = UserDirectory.alive?(char_id)
UserDirectory.send(char_id, {:system_msg, “test”})
45. 초기 프로세스 구조
지역 프로세스
인터랙션용 캐릭터
유저(캐릭터) 프로세스유저 프로세스의 이동, 공격 인터랙션
➔ 지역(채널) 프로세스
46. 이동, 공격 인터렉션
서버 부하의 대부분을 차지
채널 프로세스로 동기화 비용 올라감
채널 프로세스로 부하가 집중
지역 프로세스
유저(캐릭터) 프로세스
47. 개선1 프로세스 구조
액터 모델 극대화
➔ 지역 프로세스 집중 부하 분산
유저 프로세스 간 직접 인터렉션
48. 유저 프로세스 간 공격
1. 대상 프로세스로 공격 메시지 전달은 디렉토리 서비스로
2. 유저 프로세스 데이터 변경 주체 (ownership)는 자기자신
CharDirectory.send(target_id, {:attack, attacker, 1002})
49. 피격자 데이터 변경
공격 {:attack, attacker, attack_type} 데미지 계산 (block, miss, critical …)
데미지 후 효과 (버프, 상태, 스탯 계산 …)
피격자 프로세스
공격자 정보를 가지고 피격자는 데미지 처리하고 효과 적용
공격 응답에 대한 패킷 브로드 캐스팅
공격자 프로세스
공격 패킷을 받아서 피격자 프로세스로 전달
50. 공격자 데이터 변경
공격 {:attack, attacker, attack_type} 데미지 계산 (block, miss, critical …)
데미지 후 효과 (버프, 상태, 스탯 계산 …)
공격자에 대해 카운터 버프 걸기!
공격자 프로세스
공격 응답에 대한 정보를 토대로 버프 걸기, 공격자의 상태 변경
공격응답 {:attack_reply, damage_list}
52. 공격자의 공격 ID 발급
공격 {:attack, attack_id, attacker, attack_type}
피격자 프로세스 리스트 로 공격 패킷 만 보내면?
공격응답 {:attack_reply , attack_id, damage_list}
53. 유저 프로세스에서 이동
# 이동 패킷 핸들러
def process_move(state, pos, rot) do
old_pos = state.char.pos
# 새로운 이동 정보 변경
state = put_in(state.char.pos, pos)
state = put_in(state.char.rot, rot)
# 지역 관리 프로세스로 이동
Grid.move_char(state.char, old_pos, pos)
# 주변 브로드캐스팅
Broadcast.do(state, {:move, state.char.char_id, pos, rot})
end
54. 개선2 지역 관리 프로세스
지역이동 빈도 < 브로드캐스팅 빈도
인터랙션 행위 자체가 메시지
➔ 메시지 병목
주변에 누가 누가 있는지?
유저 프로세스에서 브로드캐스팅 목록 유지
55. 효과
1. 채널 프로세스의 집중되는 병목 분산
2. 채널 프로세스 버그에 의한 서비스 Down 사라짐
3. 채널 프로세스 비대화된 코드 제거
성능, 안정성, 생산성 증가
57. 개발에서 배포까지 OTP 로
OTP (open telecom platform)
서버 구축에 필요한 일련의 라이브러리 모음, 프레임워크
GenServer - Generic Server
Memory Storage - ETS (erlang term storage)
Database - Mnesia (distributed DBMS)
Profiling system, debugger
Crypto, Internet protocol
58. 매크로 macro meta programming
AST라는 문법으로 런타임 시 코드를 생성 컴파일 실행 가능
DSL (domain Specific Language) 정의 가능
달빛. 치트 정의를 매크로를 이용해 단순화
59. 인터렉티브 모드 iex
서버를 인터렉티브 모드(shell)로 실행 가능
Shell에서 함수를 호출하여 바로 결과를 확인
프로세스 메트릭 확인 가능
프로세스 트리 확인
프로세스별 성능 / 메모리
60. 동적 코드 리로딩
클라이언트가 접속한 상태에서 실시간 코드를 수정하여 리로딩
이리저리 고쳐가면서 테스트
iex> r Item
warning: redefining module Item (current version loaded from _build/dev/lib/item/ebin/Elixir.Item.beam)
lib/item.ex:1
{:reloaded, Item, [Item]}
64. 아직은 마이너 언어
많이 알려지지 않았음
다른 프로젝트,
다른 회사에서 사용할 수 있을까?
매년 Erlang, Elixir 컨퍼런스가 개최
반년 주기로 메이저 업데이트
stack overflow Developer Survey Results 2019
https://insights.stackoverflow.com/survey/2019#technology
65. 느슨한 타입 검사
런타임시 발견되는 예외와 에러
Elixir 감시자 프로세스로 시간을 벌어줌
코드 짜놓고
한번은 테스트
하잖아요?
66. 도구로 부터 도움
Error Tracking System
사이드 이펙트 없는 코드
한번이상 실행되면 대부분 발견되는 문제
충분한 QA로 대부분의 에러를 잡아냄
67. 우리가 노력해야 할 것
유닛 테스트로 코드 커버리지를 높여 두는 것
콘텐츠 마다 담당 개발자가 작성하는 유닛 테스트
유닛테스트 실패는 곧 배포 실패
68. 여러 곳에서 활용
• 인스턴스 메시징 서버 (Elixir, Erlang)
• 메시지 큐 (Erlang)
• 채팅 서버 (Erlang)
• 소셜 네트워크 서비스 (Elixir)
69. 생산성 증가
빠른 빌드. 클린 빌드 15초 내외
군더더기 없는 문법 (우연적 복잡성을 줄여줌)
2019.3월
파일수 라인수 언어
달빛 500개 11만줄 Elixir
문명 3,300개 65만줄 C++
배타적인 기능 제외
70. 빠른 이터레이션
적극적 유닛 테스트 활용
현재 기본 로그인, 인게임 전투, 각종 기본 콘텐츠에 대한 유닛테스트 200여 가지
로그인이
안되요.
입장이 안되요. 전투가
안되요.
서버로 인한 이런 문제는 이제 잘 없는데... 사람들은 잘 모름 흐흐
71. 소소한 행복
메모리 문제에서 해방 메모리 릭은 해결 못함
메모리 침범에 의한 알수 없는 크래시 없음
순수한 함수 덕택에 발생한 것은 재현하기 쉬움
타이밍 문제로 인한 고생이 덜함
삶의 질이
나아지는 듯