• 2009-01-27

    Google App Engine Overview

    google app engine 은 파이썬 2.5 기반의 웹 개발 프레임워크이다. 사실 수많은 웹 개발환경이 많고 많은데 유독 이놈이 눈에 띄는 건, 기본적으로 구글에서 제공하는 다양한 분산 기반의 서비스들을 손쉽게 사용할 수 있기 때문이다.

    아마도 구글 앱스를 사용중인 회사에서 인트라넷이나 백오피스 웹서비스를 만들어야 한다면 이놈을 쓰면 편해질 것 같다. 대충 훝어본 Service API 들을 요약해보겠다.

    Datastore

    구글 데이터 저장 인프라(?)에 정보를 저장하고 읽어오는 API.  객체 기반 쿼리와 SQL을 닮은 GQL 기반 쿼리를 제공한다. yaml 로 인덱스도 정의할 수 있고, FK와 트랜잭션도 지원한다.

    class Person(db.Model):
        first_name = db.StringProperty()
        last_name = db.StringProperty()
        city = db.StringProperty()
        birth_year = db.IntegerProperty()
        height = db.IntegerProperty()
    
    q = Person.all()
    q.filter("last_name =", "Smith")
    q.filter("height <", 72)
    q.order("-height")
    
    # The GqlQuery interface prepares a query using a GQL query string.
    q = db.GqlQuery("SELECT * FROM Person " +
        "WHERE last_name = :1 AND height < :2 " +
        "ORDER BY height DESC",
        "Smith", 72)
    

    Memcache

    매번마다 DB에서 읽어오면 성능이 떨어지니까, 어느 정도 변화가 적은 애들은 DB에서 읽어온 다음 Key-Value 기반의 메모리 캐시에다가 저장했다가 필요할 때마다 재빨리 읽어온다. 별거 아닌 것처럼 보이지만, 이게 분산 서버에서 모두 참조가 가능하다.. 어쨌거나 문자열키로 특정 객체를 꺼내오는 이런 패턴은 게임 서버에도 자주 써야 할 것 같다. (요즘은 변수 선언하는게 어찌나 귀찮은지..)

    def get_data():
        data = memcache.get("key")
        if data is not None:
            return data
        else:
            data = self.query_for_data()
            memcache.add("key", data, 60)
            return data
    

    URL Fetch

    다른 웹서버에다가 URL 기반 요청을 해서 결과를 읽어올 수 있다. 즉, 다른 웹서비스들과의 연동이 가능하다는 이야기. 

    url = "http://www.google.com/"
    result = urlfetch.fetch(url)
    if result.status_code == 200:
        doSomethingWithResult(result.content)
    

    Mail

    손쉽게 메일을 보낼 수 있다. 파일 첨부도 가능하다고는 하나, 단순하니까 예제는 생략.

    Images

    업로드된 이미지의 크기 변경, 회전, 상하좌우 뒤집기, 자르기, 컨트라스트/색상 보정 등의 조작이 가능하다. 이것도 있어보이는 기능이지만 일단 생략.

    Google Accounts

    구글 계정과의 연동 지원. 별명, 이메일 주소 등을 얻어오는 기능이다.

    기타 구글 Data API 연동

    이게 가장 핵심적인 기능인데, 대충 아래와 같은 API 들을 연동할 수 있다. (그런데 이걸 갖고 뭘 어떻게 할 것인가가 사실은 제일 중요하겠지..)

    레일즈 등 다른 웹 프레임워크에 비해서 좀 부족한 점이 많지만,  구글의 다양한 서비스들과의 연동과 분산에 강한 웹 서비스를 만들어야 한다면 좋은 선택이 될 것 같다. 사실 웹 시뮬레이션 게임을 만들때 좋을 것 같아서 살펴봤는데, 역시 GUI 표현이 걸림돌이 될 것 같다. 차트 API 정도라면 비지니스 관련 웹서비스는 어째 될 것 같은데, 게임은 좀...  

    API 목록을 보다 보니 특이한 서비스가 좀 많다. 다들 한번쯤 구경해보면 좋을지도?

  • 2009-01-27

    Erlide

    Eclipse logo

    이클립스의 얼랭 개발환경(perspective)인 erlide를 사용하면, 매번마다 c(module) 로 컴파일하는 번거로움에서 벗어날 수 있다. 그냥 Save 만 하면, 알아서 코드를 컴파일하고 경고나 오류를 보여주기 때문이다. 일단 매뉴얼에 써 있는 대로 설정하고 나면, 이클립스 IDE의 기능들을 간단히 쓸 수 있으므로, 얼랭 초보라면 반드시 eclipse 부터 설치하기를 권장한다.

    어차피 매뉴얼에 다 나오는 내용인데, 핵심 기능들만 간단하게만 소개하자면

    • gen_server, gen_fsm 등 OTP 기본 템플릿 제공
    • F3 - 함수 정의/선언으로 바로가기
    • Ctrl + I - 자동 들여쓰기
    • Ctrl + Space - 자동 완성
    • Alt + O - 개요 보기
    • Ctrl + Up/Down - 콘솔창에서 히스토리 기능
    • 브레이크 포인트는 라인 맨 앞을 더블클릭
    • 디버깅 끝낼 때에는 perspective 를 다시 얼랭으로 전환할 것

    특히 놀라운 것은, (사실 erl 콘솔에서도 볼 수 있지만) Process List View 에서 보면 무려 36개의 얼랭 프로세스들이 떠 있다는 점이다. 루아나 파이썬처럼 그냥 달랑 스크립트만 실행되는 게 아니라는 건 짐작하고 있었지만, 조금은 부하가 걱정될 정도로많이 떠 있긴 하다.

  • 2009-01-27

    Change the Battle Field

    MS, Google, Intel, IBM, Adobe, Dell, Sony, Sanyo... 놀라운 감원 쯔나미는 겨우 시작일 뿐이다.

    성장 동력이 없거나 멈춘 국내 게임 회사들이, 직원 월급을 못주고 있다는 첩보가 속속 입수되고 있다.  정말 대충 예상해본 것들이 슬슬 현실로 다가 오고 있다는 이야기다. 그나마 반가운 소식은 게임이 불황에도 웃음지을 수 있는 몇 안되는 산업이라는 뉴스 정도다.  

    혹자는 힘들 때일수록 몸집을 줄이고 직원 교육에 투자하라고 조언하지만, 내가 사장이라도 그건 쉽지 않은 일이다.  마구 사람을 잘라내고 있는 서구 기업들은 과연 바보라서 그러는 걸까. 그럼 우리는 이 추운 겨울을 어떻게 견디어내야 할까? 개인적으로도 선 거두절미 후 줄탁동시를 시행해야 한다고 본다. 즉,

    • 무엇보다도 가장 먼저 바보 C급 인재들을 내보낸다. 기회 비용을 고려해 봤을 때, 무작정 열심히 일하는 C급 인재야말로 회사의 최대 적이다. 덤으로 불만분자들도 함께 처분하면 더욱 좋다. (뜨끔!!)
    • A급 리더들은 쓰다듬어주기만 해도 알아서 잘 할테니, 웅크리고 있는 B급 인재들에게는 인센티브 강화 같은 당근과 **** 등의 채찍을 제공한다. 개인적으로 테라의 공개된 영상에 많은 감명을 받았는데, 외부의 공격(?)이 내부의 결속을 강화시킨 좋은 예가 아닐까 한다. 
    • 돈, 지식, 노하우, 그리고 열정적인 핵심 인재들 등 축적할 수 있는 모든 리소스들을 아껴서, 더욱 더 길어질 겨울에 대비한다. 아무리 생각해도, 이번 겨울은 10년짜리다. 
    • 부동산 난개발과 같은 불필요한 중복 개발을 자제하고, 제대로된 게임을 최대한 빨리 출시할 수 있는 프로세스를 다진다. 이걸 갖춘 회사만이 10년을 살아남을 게다. 결과적으로 개발을 투명하게 바라볼 수 있게 해주는, 애자일 프로세스의 전사적 도입도 좋은 방법이 되겠지만, 준비되지 않은 상태에서는 오히려 반감만 불러일으킬 확률이 99.999% 되겠다.
    • 무엇보다도 전장 플랫폼을 바꾼다. 살아남는 자가 강한 것처럼, 하고 싶어 하는 것보다는 잘하는 분야를 계속 파나가고, 그걸 다른 플랫폼으로 옮겨가는 전략은 어떨까. 돈먹는 하마인 MMO, 다 비슷비슷한 캐주얼, 횡스크롤 액션..  새로운 성장 동력 찾기가 쉽지 않은 건 알고 있는데, 너무 멀리서 찾다가 피같은 돈을 다 날려먹으면 곤란하다. 물론 와우를 능가하는 MMO를 만들어서 년간 1조씩 벌어들여도 되겠지만. 

    뻔한 이야기를 시니컬하게 쓰면 다들 우러러 보는 게 요즘 트렌드이길래, 나도 덩달아서 비스무리하게 써 봤다. 그러나 요즘은 불유쾌한 예언을 하는 자가 잡혀 들어가는 세상인지라, 또 쓸데없이 오해 크리나 받지나 않을까 해서 고이 모셔뒀었는데, 오늘 또 ING 7천, 필립스 6천, GM 2천 등 총 7만명짜리 GLOBAL JOB CUTS 이 있었다길래 어째 으스스해서 공개해본다.

  • 2009-01-20

    boost tuple based serialization

    boost serialization를 사용하려면 이모저모로 귀찮은 점이 많아서, 생각해본 것이 파이썬의 struct 라이브러리와 같은 느낌으로 프로토콜 포맷을 정의하고, 이걸 boost tuple 코드로 뽑아낸다는 아이디어이다.

    대강의 포맷은

    Format C Type Python
    x pad byte no value
    c char string of length 1
    b signed char integer
    B unsigned char integer
    ? _Bool bool
    h short integer
    H unsigned short integer
    i int integer
    I unsigned int integer or long
    l long integer
    L unsigned long long
    q long long long
    Q unsigned long long long
    f float float
    d double float
    s char[] string
    p char[] string
    P void * long

    가령 로그인 메시지를 "ppl" 이라는 포맷으로 정의하면, 파서는 이걸 읽고

    typedef boost::tuple<string,string,long> msgLogin;
    

    이라는 클래스를 정의한다는 거다. 요기에 좀 더 기능을 첨부하자면,

    • 64 bit integer
    • datetime, smalldatetime 등 T-SQL 타입 지원(또는 매핑)
    • STL 지원 : list, vector, map, set
    • 비트 연산 지원 : B3은 바이트 타입인데 3비트만 읽고 쓴다는 뜻이 된다.

    단점이라면 내부 멤버 접근을 userid, passwd 같은 이름 기반이 아니라 get<1>, get<2> 처럼 해야 한다는 점인데, 이건 좀 심하게 귀찮긴 하겠다. 역시 이름 기반 접근이 그나마 컴파일러가 검증해주니 제일 좋은 방법인가.

    자. 만들어주세요.

  • 2009-01-18

    Erlang/OTP UDP Server

    얼랭으로 만든, 가장 단순한 UDP Echo 서버와 클라이언트는 이렇게 생겨먹었다. (워드프레스에서 들여쓰기가 자꾸 날아가는 버그가 있으므로, 귀찮아서 고치지는 않겠다.)

    
    -module(udp_server).
    -compile(export_all).
    
    init(ServerPort) ->
    {ok, Sock} = gen_udp:open(ServerPort,[binary,inet]),
    loop().
    
    loop() ->
    io:format("server_loop~n"),
    receive
    {udp, Sock, ClientIP, ClientPort, Packet } ->
    io:format("~p:~p ==> ~p~n",[ClientIP,ClientPort,Packet]),
    ok = gen_udp:send(Sock,ClientIP,ClientPort,Packet),
    loop()
    end.
    
    
    
    -module(udp_client).
    -compile(export_all).
    
    init(ServerPort) ->
    {ok, Sock} = gen_udp:open(0),
    gen_udp:send(Sock,"localhost",ServerPort,"Hello World!"),
    loop().
    
    loop() ->
    io:format("waiting response...~n"),
    receive
    {udp, Peer, PeerIP, PeerPort, Packet } ->
    io:format("~p:~p ==> ~p~n",[PeerIP,PeerPort,Packet])
    after 1000 ->
    io:format("timeout..~n")
    end.
    
    

    여기에서 바로 OTP로 만들겠다고 뛰어들었다가 삽질을 한 후,  Building_a_Non-blocking_TCP_server_using_OTP_principles구글에서 검색한 gen_udp 예제을 참고해서 겨우 성공한 게 다음의 코드이다.

    
    
    -module(udp_otp_server).
    -compile(export_all).
    
    % stand-alone server version
    start() ->
    gen_server:start({local, ?MODULE}, ?MODULE, [], []).
    
    % supervision tree version
    start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
    
    init([]) ->
    process_flag(trap_exit,true),
    {ok, Sock} = gen_udp:open(1111,[binary,inet]),
    io:format("udp server started...~n"),
    {ok,0}.
    
    handle_call(Request, From, State) ->
    io:format("~p~n",[Request]),
    {noreply,State}.
    
    handle_cast(_Msg,State) ->
    io:format("handle_cast(~p)~n",_Msg),
    {noreply, State}.
    
    %% udp message should be handled here
    handle_info({udp,Sock, ClientIP, ClientPort, Packet}, State) ->
    io:format("~p:~p ==> ~p~n",[ClientIP,ClientPort,Packet]),
    ok = gen_udp:send(Sock,ClientIP,ClientPort,Packet),
    {noreply, State}
    ;
    
    handle_info(_Info,State) ->
    io:format("handle_info~n"),
    {noreply, State}.
    
    terminate(_Reason,N) ->
    io:format("stopping...~n"),
    ok.
    code_change(_OldVsn,N, _Extra) -> {ok,N}.
    
    

    핵심 요지는, UDP 메시지는 handle_call 이나 cast 가 아니라, handle_info에서 처리해야 한다는 점이다. 요즘 업.무.가. 바.빠.서. 얼랭을 3주 정도 못봤는데 또 서서히 기억이 소멸하기 시작했다. OTP에서는 함수의 리턴값 규칙을 정말 잘 맞춰야 하는 관계로, 다음 번부터는 gen_server 템플릿 하나 정도는 갖고 시작해야겠다.