자바 서블릿 컨테이너의 Comet 지원 3 - Resin

이전글

Resin 은 버전 3.1에서 Comet 모델을 지원한다.  Tomcat 과 마찬가지로 javax.servlet.Servlet 을 확장한 인터페이스를 제공하는 방식이다.  com.caucho.servlet.comet.CometServlet 이 그것인데 이 인터페이스에는 다음과 같은 메서드들이 있다.

public interface CometServlet extends Servlet {
  boolean service(ServletRequest request, ServletResponse response,
                            CometController controller) throws ServletException, IOException;
  boolean resume(ServletRequest request, ServletResponse response,
                             CometController controller) throws ServletException, IOException;
}


Tomcat 과 마찬가지 방식으로 이 인터페이스를 구현한 서블릿은 요청을 처리할 때 기존 서블릿의 service 메서드를 호출하지 않고 이 인터페이스의 service 메서드를 호출한다.  service 메서드의 호출 결과가 true 가 되면 요청 처리는 suspend 되고 스레드는 풀로 되돌아간다.  CometController 의 wake 메서드를 호출하면 요청 처리는 재개되어 resume 메서드가 실행된다.  resume 메서드가 true 를 반환하면 또 다시 suspend 되어 wake 를 기다리게 되고 false 를 반환하면 요청 처리가 끝나게 된다.

service, resume 메서드에 인자로 들어오는 CometController 객체는 service, resume 그리고 다른 이벤트 대기 스레드 사이에서 공유하여 신호를 보낼 수 있고(wake 메서드) 서로간 데이타 교환을 할 수도 있다(setAttribute, getAttribute).

Jetty 나 Tomcat 과는 어떻게 다른지 같은 채팅 예제로 실제 사용법을 알아보겠다. ChatServlet 과 chat.jsp 는 Jetty 의 경우와 같고 BroadcasterServlet 은 다음과 같다.

BroadcasterServlet.java
...
@Override
public boolean service(ServletRequest request, ServletResponse response,
                            CometController controller) throws ServletException, IOException {
    HttpServletResponse res = (HttpServletResponse) response;
    res.setContentType("text/html; charset=utf-8");
    messageSender.addSession(controller);
    return true;
}

@Override
public boolean resume(ServletRequest request, ServletResponse response,
                             CometController controller) throws ServletException, IOException {
    messageSender.removeSession(controller);
    String message = (String) controller.getAttribute("message");
    HttpServletResponse res = (HttpServletResponse) response;
    PrintWriter out = res.getWriter();
    out.println(message);
    res.flushBuffer();
    return false;
}
...


service 메서드에서 최초 요청 처리를 처리하면서 인자로 넘어온 CometController 객체를 messageSender 객체에 저장하여 이벤트 처리 스레드로 넘긴다.  이후 CometController 의 wake 메서드가 호출되면 resume 이 실행되고 여기서 실제 데이타를 전송한다.  전송할 데이타는 이벤트 처리 스레드에서 CometController.setAttribute 으로 넘기고 여기서는 getAttribute 로 받는다.

다음은 MessageSender 클래스이다.

MessageSender.java
public class MessageSender implements Runnable {
  private final BlockingQueue<String> messages =
                         new LinkedBlockingQueue<String>();
  private final Set<CometController> sessions =
                         new CopyOnWriteArraySet<CometController>();

  private volatile boolean running = true;

  public void stop() {
    this.running = false;
  }

  public void sendMessage(String message) {
    try {
      messages.put(message);
    } catch (InterruptedException ignore) {
      // ignore
    }
  }

  public void addSession(CometController controller) {
    sessions.add(controller);
  }

  public void removeSession(CometController controller) {
    sessions.remove(controller);
  }

  @Override
  public void run() {
    while (running) {
      String message = null;
      try {
        message = messages.take();
      } catch (InterruptedException ignore) {
        // ignore
      }
      for (CometController controller : controllers) {
        controller.setAttribute("message", message);
        controller.wake();
      }
    }
  }
}


MessageSender 는 Jetty 의 경우와 비슷하다.  CometController 들을 sessions 에 저장해 두고 채팅 메시지가 들어오면 이를 CometController 에 저장(setAttribute) 한 후 wake 를 호출해서 요청 처리가 resume 되게 한다.

Resin 의 Comet 지원은 Jetty 와 비교하면 직관적으로 이해하기 쉽다.  또한 Tomcat 처럼 번거롭지도 않다.  세 서블릿 컨테이너의 Comet 지원 중에서 가장 사용하기가 쉬운 것 같다.

by Corund | 2008/08/01 09:35 | 트랙백

자바 서블릿 컨테이너의 Comet 지원 2 - Tomcat

이전글

Tomcat 에서는 버전 6.0의 Advanced IO 지원을 통해 Comet 모델을 효과적으로 구현할 수 있게 되었다. Tomcat 6.0 에서는 Jetty 와는 달리 서블릿을 확장하는 형태로 지원하고 있다.

javax.servlet.Servlet 인터페이스를 확장한 org.apache.catalina.CometProcessor 가 그것인데 이 인터페이스는 Servlet 인터페이스를 상속한 것이다. 이 인터페이스에는 event(CometEvent) 메서드가 있는데 이 인터페이스를 구현한 클래스가 서블릿으로 등록되어 있으면 Tomcat 은 요청을 처리할 때 기존 서블릿의 service(...) 메서드를 호출하지 않고 이 event(...) 메서드를 호출하게 된다. 이 메서드는 한 요청 처리 트랜잭션에서 여러번 호출될 수 있으며 이벤트 정보가 인자로 넘어오게 된다. 이 인자로 넘어온 객체를 통해 여러 정보를 알 수 있고 IO도 처리할 수 있다. 입력(input)은 이 이벤트 처리 메서드 안에서 이루어져야 하지만, 출력(output)은 이 이벤트 처리 메서드 밖에서도 처리할 수 있다.

CometProcessor 를 사용하기 위해서는 Tomcat 설정을 조금 손봐야 한다. CometProcessor 는 Tomcat connector 가 Native 또는 NIO connector 여야 한다. 자바 1.4 버전 이상이라면 NIO 를 쓰면 되며 Tomcat 설정 파일을 다음과 같이 수정하면 된다.

<!-- 기존 설정. 아래와 같이 바꾼다.
<connector connectiontimeout="20000" port="8080" protocol="HTTP/1.1" redirectport="8443"/>
-->
<connector connectiontimeout="20000" port="8000">
     protocol="org.apache.coyote.http11.Http11NioProtocol" useComet="true" redirectPort="8443"/>


event 메서드는 BEGIN, READ, END, ERROR 이벤트가 발생할 때 호출되는데 이때 인자로 org.apache.catalina.CometEvent 객체가 넘어온다. 이 객체에는 위의 Event Type 을 얻는 메서드와 HttpServletRequest, HttpServletResponse 객체를 얻는 메서드 등이 있다.

이전 Jetty 의 경우와 마찬가지로 채팅 예제로 실제 사용법을 알아본다. 먼저 ChatServlet 과 chat.jsp 는 이전 jetty 의 경우와 같다. BroadcasterServlet 은 CometProcessor 를 사용해야 하는데 코드는 다음과 같다.

public class BroadcasterServlet extends HttpServlet implements CometProcessor {
...
  @Override
  public void event(CometEvent event) throws ServletException, IOException {
    HttpServletRequest request = event.getHttpServletRequest();
    HttpServletResponse response = event.getHttpServletResponse();
    String sessionId = request.getSessionId();

    if (CometEvent.EventType.BEGIN == event.getEventType()) {
      // 요청을 최초로 처리할 때 호출됨.
      response.setContentType("text/html; charset=utf-8");
      messageSender.addSession(sessionId, event);
    } else if (CometEvent.EventType.ERROR == event.getEventType()) {
      // IO 에러가 발생했을 때.
      messageSender.removeSession(sessionId);
      event.close(); // 요청 처리 완료.
    } else if (CometEvent.EventType.END == event.getEventType()) {
      // 요청 처리가 완료되었을 때
      log("End event");
    } else if (CometEvent.EventType.READ == event.getEventType()) {
      log("Read event");
    }
  }
...
}


요청을 최초로 처리할 때 BEGIN 이벤트가 발생하며 이때 Request 정보를 읽는 작업을 한다. 여기서는 채팅 메시지가 있을 때 메시지를 보낼 수 있도록 event 객체를 messageSender 객체에 저장해 놓는 작업도 한다.

요청에서 읽을 데이타가 준비되었을 때 (즉 request.getInputStream() 등) READ 이벤트가 발생하며, 에러가 발생했을 때 ERROR 이벤트가 요청 처리가 완료되면 END 이벤트가 발생한다. 여기서는 ERROR 이벤트가 발생하였을 때 관련 정보를 messageSender 에서 삭제하고 요청을 마치는 작업을 한다.

MessageSender 클래스는 다음과 같다. Jetty 의 경우와 조금 다른데 메시지를 실제로 보내는 작업도 여기서 처리하고 있다. CometProcessor 에 원하는 때 이벤트를 발생시킬 수 없기 때문이다. 따라서 이벤트가 발생할 때 출력을 하기 위해 미리 CometEvent 객체를 저장하여 HttpServletResponse 객체를 얻어 출력을 한다.

MessageSender.java
public class MessageSender implements Runnable {
  private volatile boolean running = true;
  private final BlockingQueue<String> messages =
                                          new LinkedBlockingQueue<String>();
  private final Map<String, CometEvent> sessions =
                                          new ConcurrentHashMap<String, CometEvent>();
  private final ExecutorService executor = Executors.newFixedThreadPool(5);

  public void send(String message) {
    try {
      messages.put(message);
    } catch (InterruptedException ignore) {
      // ignore
    }
  }

  public void addSession(String id, CometEvent event) {
    sessions.put(id, event);
  }

  public void removeSession(String id) {
    sessions.remove(id);
  }

  public void stop() {
    this.running = false;
    this.executor.shutdown();
  }

  @Override
  public void run() {
    while (running) {
      String message = null;
      try {
        message = messages.take();
      } catch (InterruptedException ignore) {
        // ignore
      }
      for (String id : sessions.keySet()) {
        executor.submit(new Task(id, message));
      }
    }
  }

  private class Task implements Runnable {
    private String sessionId;
    private String message;

    public Task(String id, String msg) {
      sessionId = id;
      message = msg;
    }

    public void run() {
      CometEvent event = sessions.get(sessionId);
      if (null == event) {
        return;
      }
      HttpServletResponse response = event.getHttpServletResponse();
      PrintWriter out = null;
      try {
        out = response.getWriter();
        out.println(message);
        out.flush();
        response.flushBuffer();
      } catch (IOException naive) {
        naive.printStackTrace();
      } finally {
        try { out.close(); } catch (Exception ignore) {}
        try { event.close(); } catch (Exception ignore) {}
        sessions.remove(sessionId);
      }
    }
  }
}


Jetty 의 경우와 달리 Tomcat 은 Servlet 인터페이스를 확장하여 웹 요청 단계에 따라 이벤트를 발생시키는 방식으로 작동한다. 네트워크 서버가 많이 취하고 있는 방식이라 쉽게 이해할 수 있다. 그러나 실제 프로그래밍을 하려면 번거로운 점이 있다. Jetty 처럼 원하는 때 resume 을 시킬 수 없기 때문에 long polling 처럼 원하는 때 출력을 하려면 서블릿 코드 밖에서 출력을 해야 하며 따라서 직접 스레드를 관리할 필요가 생긴다. 또한 Error 시에 적절히 자원을 정리하는 코드도 필요하다.

정리하자면 Tomcat 의 Comet 지원은 일반 네트워크 서버 스타일에 가깝다고 볼 수 있다. 좀더 웹 프로그래밍 스타일에 가깝게 만들었으면 좋지 않았을까 하는 생각이 든다.

by Corund | 2008/07/30 19:31 | 트랙백 | 핑백(1)

자바 서블릿 컨테이너의 Comet 지원 1 - Jetty

이전글: Comet 에 대하여

Jetty 는 버전 6.0에서부터 Continuation 이라는 것을 도입해서 Comet 스타일의 웹 프로그래밍을 효과적으로 구현할 수 있게 되었다.  jetty의 Continuation 을 이용하면 웹 요청을 처리를 중지(suspend)시켜서 대기 상태로 만들었다가 후에 필요할 때 다시 재개(resume)시킬 수 있다.  웹 요청 처리를 중지(suspend)시키면 요청을 처리하던 스레드는 다시 스레드 풀(Thread Pool)로 되돌아가 다른 요청을 처리할 수 있게 된다.  이로서 장시간 유지되는 HTTP 연결을 더 적은 수의 스레드로 효과적으로 처리할 수 있게 된다.

Continuation 은 scheme 등의 언어에서 지원하던 개념인데, jetty 에서는 이와 비슷하긴 하지만 꼭 같지는 않다.  요청 처리를 중지(suspend)시킨 후 재개(resume)하면 바로 중지시킨 그 지점에서 다시 진행하는 것이 아니라 요청 처리 체인(FilterChain)을 다시 처음부터 진행한다. 이것 때문에 프로그래밍에 약간 신경써야 할 것들이 있다. 

실제 Jetty 의 Contiuation 을 이용하여 채팅을 구현한 예로 설명을 진행하겠다.

먼저 채팅 페이지를 보여주고 채팅 메시지를 받는 서블릿이다.

ChatServlet.java
...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ... {
  request.getRequestDispatcher("/WEB-INF/jsp/chat.jsp").forward(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ... {
  String name = request.getParameter("name");
  String text = request.getParameter("text");

  String message = "<b>" + name + "</b>: " + text + "<br>";
  messageSender.sendMessage(message);

  response.setContentType("text/html");
  response.getWriter().println("OK");
}
...


채팅 페이지는 다음과 같다.

chat.jsp
...
<script type="text/javascript">
    function pollMessage() {
        new Ajax.Updater({ success: 'panel' }, '/broadcaster', {
            method: 'get',
            insertion: 'bottom',
            onComplete: pollMessage
        });
    }
    function sendMessage() {
        new Ajax.Request('/chat', {
            method: 'post',
            parameters: { nick: $F(name'), text: $F('text') },
        });
        $('text').clear();
        return false;
    }
</script>
</head>
<body onload="pollMessage()">
<h1>채팅 페이지</h1>
<div id="panel"></div>
<form action="" method="POST" onsubmit="return sendMessage()">
    Nick <input type="text" name="name" id="name" size="10">
    <input type="text" name="text" id="text" size="40">
    <input type="submit" value="보내기">
</form>
...


ajax 를 이용해서 채팅 메시지를 ChatServlet 으로 보낸다.  그리고 BroadcasterServlet 에 long polling 으로 접속해서 메시지를 받아 이를 패널에 출력한다.  pollMessage 함수가 Comet long polling 을 하는 코드이다.

ChatServlet 은 chat 메시지를 받으면 이를 messageSender 객체로 보낸다. messageSender 객체는 MessageSender 클래스의 인스턴스로 미리 ServletContext 에 저장해 놓고 최초 context 기동시에 기동시킨다.

다음은 Long Polling 으로 접속된 클라이언트에 채팅 메시지를 뿌려주는 BroadcasterServlet 이다. 이부분에 jetty의 Continuation 이 사용되었다.

BroadcasterServlet.java
...
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ... {
    Continuation continuation = ContinuationSupport.getContinuation(request, null);
    if (! continuation.isPending()) {
        // new session start
        response.setContentType("text/html; charset=utf-8");
        messageSender.addSession(continuation);
    }
    continuation.suspend(0); // request suspend
    // resumed
    String message = (String) continuation.getObject();
    PrintWriter out = response.getWriter();
    out.println(message);
    out.flush();
    response.flushBuffer();
}
...


여기서 messageSender 객체는 위의 ChatServlet 의 messageSender 객체와 같은 객체이다.

최초로 요청이 처리될 때에는 continuation.isPending() 메서드가 false 가 된다.  그리고 Contiunation 이 suspend 되었다가 resume 되어 다시 요청 처리 체인이 진행될 때에는 isPending() 메서드가 true 가 된다(다시 suspend 메서드가 호출될 때까지).  이를 이용하여 suspend 호출 이후 부분만을 다시 실행시키는 것처럼 할 수 있다.

continuation.suspend() 가 호출되면 이 요청 처리는 여기서 끝나게 된다.  그렇지만 HTTP 커넥션은 그대로 유지되고 있으며 request, response 객체도 여전히 유효한 상태이다.  이후 continuation.resume() 이 호출되면 이 요청 처리 체인은 다시 실행된다(resume 메서드는 messageSender 에서 호출한다).  이 때 continuation 객체는 이전과 같은 객체이며, 다시 suspend() 호출을 만날 때까지 isPending() 메서드는 true 가 된다(반면 최초 suspend() 호출 이전에 isPending() 메서드는 false 이다). 이를 이용하여 suspend() 이후를 진행시키는 것과 같은 효과를 낼 수 있는 것이다.

MessageSender 클래스는 다음과 같다.

MessageSender.java
public class MessageSender implements Runnable {
  private volatile boolean running = true;
  private final BlockingQueue<String> messages =
                             new LinkedBlockingQueue<String>();
  private final Set<Continuation> sessions =
                             new CopyOnWriteArraySet<Continuation>();

  public void stop() {
    this.running = false;
  }

  public void addSession(Continuation cont) {
    sessions.add(cont);
  }

  public void sendMessage(String message) {
    try {
      messages.put(message);
    } catch (InterruptedException ignore) {
      // 종료 처리를 해야 하지만 생략
    }
  }

  public void run() {
    while (running) {
      String message = null;
      try {
        message = messages.take();
      } catch (InterruptedException ignore) {
        // 종료 처리를 해야 하지만 생략
      }
      for (Continuation continuation : sessions) {
        continuation.setObject(message);
        continuation.resume();
        sessions.remove(continuation);
      }
    }
  }
}


이 클래스는 ServletContextListener 에서 최초 context 가 기동될 때 인스턴스를 생성하여 새 스레드로 시작한다.

ChatServlet 에서 sendMessage 를 호출하면 메시지가 Queue 에 쌓이게 되고 이 메시지는 메시지 처리 스레드에서 받아 continuation 객체에 저장한 다음 continuation 을 resume 한다.  그러면 BroadcasterServlet 에서 다시 실행이 되는 것이다.  continuation suspend() 호출과 resume 호출 그리고 resume 후의 진행은 모두 다른 스레드에서 진행된다.  따라서 이 사이의 데이타 교환은 Continuation 객체의 setObject 와 getObject 메서드를 통해서만 해야 한다.

Jetty 의 Continuation 방식의 Comet 지원은 기존 Servlet API 가 크게 바뀌지 않아도 된다는 장점이 있다.  그래서인지 Servlet 3.0 의 Comet 지원도 이와 비슷한 방식 - ServletRequest 를 suspend, resume 하는 방식 - 으로 논의되고 있는 것 같다.  그렇지만 약간은 억지같은 Continuation 방식으로 최초 suspend 시 이곳에서 처리가 끝난다는 점이 직관적이지 못하며 resume 을 위해 isPending 과 같은 메서드로 체크를 해야 한다는 번거로움이 있다.

Tomcat 6.0, Resin 3.1 의 방식은 훨씬 이해하기 쉬운 방식인데 이는 다음 번에 다루겠다.

by Corund | 2008/07/30 16:52 | 트랙백 | 핑백(2)

Comet 에 대하여

Reverse Ajax, Ajax Push, Two-Way-Web, HTTP server PUSH 등등으로 불리던 말이 이제 Comet 이라는 용어로 통일되어 가는 거 같다.  개념이 나온지는 꽤 되었지만 자바 서블릿 컨테이너가 이를 효과적으로 지원하지 않아 그간 관심만 가지고 있었다.  그런데 요즘 여러 서블릿 컨테이너들이 이를 지원하기 시작했고 Servlet API로의 표준화도 논의되고 있는 거 같다.  그래서 Comet 에 관련하여 한번 정리해 보고자 한다.

Comet 이란 웹 클라이언트(보통 웹 브라우저)의 명시적인 요청이 없어도 서버에서 클라이언트로 데이타를 밀어넣는(Push) 방식으로 동작하는 웹 프로그래밍 모델을 일컫는 말이다.  다만 HTTP 트랜잭션을 웹 서버가 먼저 시작할 수는 없으므로 클라이언트가 먼저 서버에 접속하여 접속을 계속 유지한 상태에서 서버가 데이타를 전송한다.  서버가 먼저 클라이언트로 접속을 열어 데이타를 전송할 수는 없다.

원래 HTTP라는 것이 웹 브라우저에서 요청을 보내면 서버가 응답을 전송하고 끝마치는 간단한 구조라 서버에서 클라이언트로 데이타를 밀어넣는 것은 구현하기가 힘들다.  이런 어려움을 어떻게든 극복하고자 여러 시도가 있었고 이들이 Comet 이라는 이름으로 정리되어 가는 중인 거다.

Comet 은 구체적인 구현 방법을 가리키는 말은 아니며 여러 방식들을 통칭하는 말이다.  HTTP server push 를 구현하는 방법은 여러 방식이 있는데 이중의 부분 집합을 Comet 이라 할 수 있다.  HTTP Server push 방법에는 다음과 같은 것들이 있다.

1. Polling
일정 간격으로 서버에 요청을 보내어 서버의 이벤트를 받는 방식이다.  요청 간격을 줄이면 거의 실시간으로 서버의 이벤트를 받을 수 있다.  그러나 서버의 자원과 대역을 많이 소모하므로 실제로 쓰기는 어려운 방식이다.

2. Long Polling
클라이언트가 서버에 접속을 하면 서버는 계속 접속을 유지하고 있다가 이벤트가 발생하면 클라이언트로 전송하고  HTTP 트랜잭션을 마친다. 클라이언트는 메시지를 받으면 다시 서버에 접속을 하여 위의 과정을 반복한다.

3. Streaming
클라이언트와 서버의 접속을 계속 유지한 채 서버에서 이벤트가 발생할 때마다 메시지를 HTTP Chunked 방식으로 보낸다.  가장 이상적인 방법이겠으나 ajax로 구현할 때 ie 에서는 제대로 작동하지 않는다(readyState = 3 이벤트가 발생하지 않는다).  iframe 을 사용하여 <script> 태그로 둘러싼 메시지를 보내는 방식을 사용할 수 있지만 이 방식은 접속 오류를 제대로 처리하기 힘들다.

이중 Long Polling 과 Streaming 을 Comet 이라고 한다.  Server PUSH 로는 Streaming 이 이상적이겠지만 위의 한계점 때문에 보통 Long Polling 방식을 주로 사용한다.  Streaming 방식을 지원하는 것이 HTML5에서 논의 중인 것으로 보여 앞으로 이것이 구체화되면 Streaming 방식으로 좀더 효율적으로 Comet 을 구현할 수 있을 것이다.

Comet 모델을 구현하는 데 또 한 가지 걸림돌은 웹 서버의 지원이다.  Long Polling 이든 Streaming 이든 HTTP 커넥션을 계속 유지할 필요가 있는데 대부분의 웹 서버(자바 서블릿 컨테이너도 포함)들은 짧은 HTTP 트랙잭션에 맞춰져 있기 때문에 Comet 을 지원하는데 효율적이지 못하다.  특히 자바 서블릿 컨테이너는 HTTP 요청이 각각 스레드에 할당되어 처리되기 때문에 Comet 을 지원하게 되면 많은 스레드가 생성되게 된다.  스레드는 대부분 단지 커넥션을 유지하고 있을 뿐 거의 동작을 하지 않다가 서버측 이벤트가 있을 경우에만 잠깐 동작할 뿐이다.  이렇게 많은 Idle 스레드가 생기면 스레드 생성 및 컨텍스트 교환에 따른 오버헤드가 무시못하게 된다.  

Comet 을 효율적으로 지원하기 위해서는 다른 고성능 서버 프로그램처럼 하나의 스레드가 여러 커넥션(이 경우 웹 요청)을 처리할 수 있어야 한다.  현재 서블릿 API에서는 이것이 불가능하다.  따라서 이에 대한 대안이 여럿 등장했다.  Comet 요청을 전용으로 처리하고자 하는 서버도 있었으며 서블릿 컨테이너 중에는 Jetty 가 최초로 Continuation 방식의 API 를 선보였다.  이후 Tomcat 6.0, Resin 3.1 에서도 각기 나름의 API 를 선보였다.  현재 Early Draft Review 상태에 있는 Servlet 3.0 에서 Jetty의 Continuation 과 비슷한 방식으로 지원이 논의되고 있다.

다음 포스트에서 서블릿 컨테이너별로 Comet 을 지원하는 기능을 살펴보고자 한다.

by Corund | 2008/07/29 19:32 | 트랙백 | 핑백(3)

Erlang 을 접해 보고


Programming Erlang: Software for a Concurrent World 를 읽었다.  이 책 한 권 읽고 Erlang을 잘 알게 되었다고 할 수는 없겠지만 어떤 것인지는 느낀 거 같다.

감상을 한마디로 말하자면 "괴물을 한 방에 쓰러뜨릴 수 있는 은빛 탄환은 없다"라는 말일 꺼다.  Erlang에 대한 소개를 간간히 접하면서 나는 Erlang에 동시성(Concurrency) 문제 해결을 위한 비책이 있는 줄로만 알았다. 부수 효과(Side Effect)가 없는 함수형 언어, 언어 차원에서 지원하는 프로세스 관리와 동시성 지원, 공유 상태가 없는 순수 메시지 패싱 방식이라는 등이 그런 것이다.  그러나 마치 마법과도 같은 위의 처방으로 동시성 문제를 획기적으로 해결하고 있다는 말은 사실이 아닌 듯 싶다.  그것보다는 모듈화와 캡슐화, 관심 사항의 분리, 그리고 잘 구현한 라이브러리라는 일반적인 처방을 잘 사용해서 좋은 시스템을 만들었다가 정답인 듯 싶다.

그전부터 Erlang에서 어떻게 부수 효과가 없이 우아하면서도 효율적인 IO를 구현했을지 궁금했다.  그리고 과연 공유 객체 없이 순수 메시지 패싱으로 어떻게 시스템 상태를 처리할지 궁금했다.  그러나 다소 심하게 말하자면 IO에 관련된 부분은 대충 얼버무리고 있다고 할 수 있고, 공유 객체가 사라진 것도 아니었다.  IO는 별로 드러나지 않게 한 것이고 공유 객체는 ETS, DETS 또는 Mnesia라는 통제된 방식으로 사용하는 게 다를 뿐이다.

그렇다고 Erlang이 형편없는 시스템이라는 건 아니다.  다만 역시 좋은 시스템은 소프트웨어 개발의 영원한 원칙 - 모듈화와 캡슐화, 적절한 분리, 라이브러리 구축 - 을 잘 따르고 있다는 걸 다시금 확인했다는 거다.  특정 기술 또는 환경을 사용한다고 해서 문제를 간단히 해결할 수 없다는 것과 더불어.

Erlang을 더 공부하고 익힐 수도 있겠지만 더 공부할 거 같진 않다.  분산 환경을 지원하기 위한 시스템은 멋지지만 언어 자체는 그다지 멋스럽지도 않고 - 만들어진지 좀 돼서 그런지 구식의 느낌도 든다. 그보다 더 오래된 Lisp은 안 그런데 말이다 - 표현력이나 확장성은 좀 제한적이라는 느낌이 든다.  그리고 나에겐 별로 재밌지도 않다.  세상에 공부할 언어는 많고 Erlang 보다 더 재밌는 언어도 많다.

by Corund | 2008/07/28 16:19 | 트랙백(1)

◀ 이전 페이지다음 페이지 ▶