레이블이 Erlang인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Erlang인 게시물을 표시합니다. 모든 게시물 표시

20130521

begin erlang

주말에 얼랭(Erlang) 한번 파볼까 싶어서 시작. 회사에서 RabbitMQ를 좀 깊이 보다 보니, 이게 결국 얼랭으로 짜여있고 OTP의 supervision tree 같은 개념을 알아야 운영이 편하더라. WhatsApp도 얼랭으로 수억 사용자를 소수의 서버로 굴린다는 얘기가 워낙 유명해서 한 번 정리하고 싶었음.

참고한 자료는 펜실베이니아 대학 David Matuszek 교수의 A Concise Guide to Erlang. 그걸 직접 따라가면서 우리나라 개발자 입장에서 다시 정리하는 글이다. 환경은 Erlang/OTP R16B (2013년 3월 릴리즈) 기준.

왜 얼랭인가

한 줄 요약: 절대 죽으면 안 되는 시스템을 위한 언어. 핵심은 세 가지로 본다.

첫째, 액터(Actor) 기반 동시성. 스레드/락이 아니라, 가벼운 프로세스 수십만개가 메시지로만 통신한다. 공유 상태가 없으니 데이터 경합이 원천적으로 없다. 우리가 PHP/Python 멀티프로세스 환경에서 메시지 큐로 우회하는 그 그림이, 얼랭에서는 언어 차원에서 기본이다.

둘째, let it crash 철학. 에러가 나면 잡지 말고 그냥 죽게 두고, 슈퍼바이저가 다시 띄운다. 우리가 익숙한 try/catch 천국과는 정반대 사고방식이라 처음에 좀 어색하다.

셋째, 핫코드 스왑. 시스템 안 내리고 코드 교체. 24/7 시스템(통신 교환기, 메시징 서버) 운영자 입장에서는 굉장히 매력적이다.

설치와 REPL

맥에서는 brew install erlang. 우분투는 apt-get install erlang. 셸 진입은 erl.

$ erl
Erlang R16B (erts-5.10.1) [source] [64-bit] ...

1> 1 + 2.
3
2> X = 10.
10
3> X = 20.
** exception error: no match of right hand side value 20 **

두번째 줄에서 X = 10을 했는데, 세번째 줄에서 X = 20을 하니 에러. 단일 할당(single-assignment) 언어라서 한 번 바인딩되면 끝이다. 변수가 아니라 사실상 상수다.

이게 얼랭의 첫 번째 컬쳐쇼크. 처음에는 짜증나지만, 디버깅할 때 "이 변수가 어디서 바뀌었나" 추적할 일이 없어서 편해진다고들 한다. 일단 메모만 해둠.

모듈과 함수 정의

REPL에서는 함수를 정의할 수 없다. 파일에 모듈로 저장해야 한다. hello.erl:

-module(hello).
-export([greet/1]).

greet(Name) ->
    io:format("~s, hello!~n", [Name]).

핵심 규칙

-module(filename).               % 파일명과 동일해야 함
-export([함수명/인자수, ...]).    % 외부 노출
-import(filename, [함수명/인자수]). % 다른 파일의 함수 사용

컴파일과 실행은 셸에서:

1> c(hello).
{ok,hello}
2> hello:greet("준영").
준영, hello!
ok

패턴 매칭

얼랭에서 가장 강력한 기능. 변수 할당도 패턴 매칭이다.

1> {ok, Value} = {ok, 42}.
{ok,42}
2> Value.
42

3> [Head | Tail] = [1, 2, 3, 4].
[1,2,3,4]
4> Head.
1
5> Tail.
[2,3,4]

리스트 분해, 튜플 분해 모두 패턴 매칭으로 해결. 함수 인자에서도 패턴 매칭이 동작한다.

factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).

여기서 두 절(clause)이 인자 패턴으로 갈린다. when은 가드 표현. C/Java의 if-else 트리를 안 쓰고도 분기가 가능. PHP/Python 짤 때마다 "함수 오버로딩 있으면 좋겠다" 싶었던 게 얼랭에는 그냥 있는 셈.

리스트 컴프리헨션

파이썬을 알면 익숙하다.

1> [X * 2 || X <- [1, 2, 3, 4, 5]].
[2,4,6,8,10]

2> [X || X <- [1, 2, 3, 4, 5], X rem 2 == 0].
[2,4]

|| 가 파이썬의 for 와 같은 역할. 뒤에 콤마로 가드 추가.

프로세스와 메시지 패싱

이게 얼랭의 진짜 묘미. spawn/1 으로 프로세스 띄우고, ! 연산자로 메시지 보내고, receive 로 받는다.

-module(echo).
-export([start/0, loop/0]).

start() ->
    spawn(echo, loop, []).

loop() ->
    receive
        {From, Msg} ->
            From ! {self(), Msg},
            loop();
        stop ->
            ok
    end.
1> Pid = echo:start().
<0.34.0>
2> Pid ! {self(), "hi"}.
{<0.32.0>,"hi"}
3> receive {_, Reply} -> Reply end.
"hi"

OS 프로세스가 아니다. 얼랭 VM 안의 가벼운 프로세스다. R16 기준 한 프로세스가 ~600워드(2KB대) 메모리만 먹는다. 수십만개 띄워도 부담 없다는 게 이래서 가능.

I/O

출력은 io:format(FormatString, ListOfData). 포맷 지정자가 좀 다르다.

~s   문자열로 출력
~w   표준 문법으로 (문자열은 정수 리스트로 보임)
~p   pretty print
~n   개행

파일 I/O

{ok, S} = file:open("data.txt", read).
Line = io:get_line(S, '').    % eof 가 나올 수 있음
file:close(S).

예외 처리

throw / exit / error 세 종류. 잡으려면 try..catch.

try
    SomeExpression
catch
    throw:What -> {caught_throw, What};
    exit:What  -> {caught_exit, What};
    error:What -> {caught_error, What};
    _:_        -> caught_anything
after
    cleanup_code
end.

다만 앞에서 말한 것처럼 얼랭 정신은 "잡지 마라, 죽고 슈퍼바이저가 살린다". try/catch 가 자주 등장하는 코드라면 설계가 잘못된 신호라고 본다.

며칠 만져보고 든 생각

문법 자체는 정말 작다. 한 주말이면 핵심은 다 본다. 어려운 건 OTP 의 design pattern (gen_server, supervisor, application) 과 함수형 사고방식이지, 언어 자체는 아니다.

당장 회사 업무에 쓸 일은 없을 것 같다. PHP/Python 도 충분하고, 우리 트래픽 수준에서 얼랭이 꼭 필요한 시스템도 아니고. 다만 RabbitMQ 운영하면서 OTP 개념을 알면 장애 났을 때 디버깅에 큰 도움이 된다. 거기까지가 일단 목표.

조금 더 나가면 ejabberd 도 한 번 띄워보고 싶다. 사내 메신저 만들 때 후보로 봤었던 적 있는데, 그때는 자바 옵션(OpenFire)이 더 친숙해서 그쪽으로 갔다. 지금 다시 본다면 ejabberd 가 더 가벼울 것 같음.

한 줄 정리: 모든 개발자가 한번쯤 만져볼 만한 언어. 다만 production 에 도입할 때는 운영 경험 있는 사람 영입부터 생각해야 함.

참고: A Concise Guide to Erlang — David Matuszek (2010)