Software Development

Firefox의 URI encoding 방식

On Encoding URI with Non-ASCII chracters 글에 오류가 있었습니다. Firefox는 현재 (2.0) URI를 UTF-8로 Percent-encoding하지 않습니다. (이 사실은 윤묵형이 지적해주셨습니다.)

Firefox의 about:config 페이지를 보면 URI encoding에 관한 두개의 옵션이 있습니다.

  • network.standard-url.encode-utf8: HTTP 요청으로 전달되는 URI를 UTF-8로 Percent-encoding할 것인가, 클라이언트 환경이 정의하는 인코딩으로 Percent-encoding할 것인가를 나타냅니다. 디폴트 값은 false입니다.
  • network.standard-url.escape-utf8: 주소표시창에 나타나는 URI를 UTF-8로 Percent-encoding할 것인가, 인코딩하지 않을 것인가를 나타냅니다. 디폴트 값은 true입니다.
  • network.standard-url.encode-query-utf8: 제가 테스트해본 결과로는 영향을 미치지 않습니다.

encode-utf8 옵션의 디폴트 값이 false이기 때문에, Firefox는 클라이언트 환경이 정의하는 인코딩으로 Percent-encoding을 수행합니다. 따라서, 한글 윈도우에서는 EUC-KR로 Percent-encoding됩니다. UTF-8 환경의 리눅스에서는 위의 옵션 값이 false라고 하더라도 UTF-8로 Percent-encoding됩니다. 따라서 IE와 firefox는 다릅니다. 아마 기존과의 호환성을 위해서 아직 false인 것 같은데, IE에서는 기본값을 사용하는 상태이므로 Firefox도 true로 가는 것이 타당하다고 생각됩니다.

한편, escape-utf8 옵션의 디폴트 값이 true이므로, Firefox의 주소표시줄에 한글을 적으면 Percent-encoding된 형태로 표시됩니다. 이 옵션의 값을 false로 만들면, 한글 그대로 표시됩니다.

IE나 표준과 호환되면서, 사용자 입장에서는 디코딩된 형태의 URI를 보고 싶다면, Firefox about:config 페이지의 network.standard-url.encode-utf8 옵션은 true로, network.standard-url.escape-utf8 옵션의 값은 false로 만들어주시면 되겠습니다. 테스트는 http://ko.wikipedia.org/wiki/대한민국 로 해보시면 됩니다. 주소표시줄에 “대한민국”이라고 나오면 성공입니다.

아직 안읽어봤지만 이 문제에 대한 다음과 같은 논의들이 있는 것 같군요.

다음번에는 웹서버별로 URI encoding에 대해서 어떻게 처리하고 있는지 알아보도록 하죠.

Firefox의 URI encoding 방식 더 읽기"

On Encoding URI with Non-ASCII characters

History of Standards concerning URI with Non-ASCII characters

1994년의 URI를 정의하는 RFC1630 ë˜ëŠ” URL을 정의하는 RFC1738을 ë³´ë©´ 알 수 있듯이, ì´ˆê¸°ë¶€í„° URI는 Non-ASCII 문자를 포함할 수 없다. 현재 우리가 Percent Encoding이라고 부르는 escaping 방식은 단지 공백이나 ì œì–´ 문자들 그리고 7bit 범위를 넘어서는 unsafe  문자들을 표현하기 위해 정의 되어있었으나, 어떤 문자 인코딩을 사용해야하는지는 정의되지 않았으며, I18N에 대해서 명확하게 언급하지는 않았다.

1998년, IETF Policy on Character Sets and Languages (RFC2277)에서 I18N을 위해 모든 프로토콜에서 모든 문자 데이터는 문자셋(charset)을 명시해야하며, UTF-8 문자집합과 인코딩을 사용할 수 있어야 한다고 명시됨으로써 인터넷 프로토콜들에 대한 ê¸°ë³¸ì ì¸ I18N 가이드라인이 만들어졌다. 그럼에도 불구하고, 이후에 업데이트된 URI를 정의하는 1998년의 RFC2396은 Escaped Encoding에 관해서 좀 더 자세하고 정밀하게 설명하고 있지만 역시 Escaped Encoding을 적용할 때 어떤 character encoding에 대한 언급은 없었다. 다행히 1999년의 W3C에서 출판한 HTML 4.01의 Appendix에서는 Non-ASCII 문자를 UTF-8로 표현하도록 ‘권장’하게 되었다. 2001년에 역시 W3C쪽 멤버 한 사람이 19th IUC(International Unicode Conference)에서 Non-ASCII 문자에 대한 UTF-8ê³¼ Percent Encoding 사용을 공식화하는 IRI (Internationalized URI)에 관한 발표를 했고, 이는 IRI specification 작업으로 이어졌다. IRI의 결과가 RFC로 결실을 맺은 것은 2005년의 RFC3986이었다. 이 때에서야 scheme-specific한 문자 인코딩에 관한 언급과 함께 새로운 scheme이 UCS로 된 텍스트를 정의할 때는 UTF-8ê³¼ Percent Encoding을 사용해야한다는 언급이 들어가게 되었다. (http scheme은 Generic URI를 사용하므로 UTF-8로 인코딩되어야함을 의미하는 것으로 해석하는 것이 적절할 것이다.)

Web Browser’s Perspective

한편, 웹페이지들의 수가 증가하고, Non-ASCII 문자들을 포함한 URI들이 한국을 포함한 비영어권의 ë§Žì€ 웹페이지들에 들어가면서, 브라우저는 이들을 어떻게 처리해야할지 고민해야만 했다. 일반적으로, 웹브라우저는 사용자가 페이지 내의 URI을 클릭했을 때, ê·¸ URL에 해당하는 페이지를 보여주기 위해서, URI에 명시되어있는 서버로 URI을 보내야한다. 브라우저가 이 URI들을 어떤 형태로 서버로 보내느냐에 따라 ì„¸ê°€ì§€ì˜ 방식으로 나눌 수 있다.

  1. Non-ASCII 문자들을 웹페이지의 인코딩대로 인코딩된 채 (그대로 포함한 채) URL를 웹서버로 보낸다.
  2. Non-ASCII 문자들을 웹페이지의 인코딩을 사용하여 Percent Encoding한 후 웹서버로 보낸다.
  3. Non-ASCII 문자들을 정해진 인코딩 (e.g. UTF-8) 을 사용하여 Percent Encoding한 후 웹서버로 보낸다.

1번안의 방식은 Non-ASCII 문자가 URI에 들어가서는 안된다는 표준에 직접적으로 배치될 뿐만 아니라,

  • 웹서버 또는 CGI 등의 웹 어플리케이션이 웹페이지의 문자 인코딩과 같은 문자 인코딩을 사용해서 URI를 디코딩하도록 ì„¤ì •ë˜ì–´ 있거나,
  • 우연찮게도 파일시스템 등에서도 같은 인코딩을 사용하고 있어야한다는 제약이 있다. (즉, 디코딩을 아예 하지 않음)

웹서버가 인코딩에 대한 지원이 빠져있는 trivial한 구현인 경우 후자의 경우가 ë°œìƒí•˜ê²Œ 된다. 이를테면 EUC-KR 웹페이지에서 나온 EUC-KR URI를 웹서버에서 그대로 넘겨주더라도 ë‹¤í–‰ížˆ EUC-KR 파일 시스템이라서 제대로 웹페이지가 보여졌을뿐, 파일시스템의 인코딩을 UTF-8로 변경하려면, 웹페이지의 인코딩까지 바꿔야한다.

2번안의 경우는 1번안의 경우와 비슷하나, 1번안에서의 후자와 같은 경우는 발생할 수 없다. 제대로 구현된 웹서버라면 URI의 해석이 íŠ
¹ì • 인코딩(Encoding A)으로 설정되어있거나 페이지의 인코딩을 따른다고 하더라도, 서버의 파일시스템이 어떤 인코딩(Encoding B)인지 인지한 후 적절하게 인코딩을 변환(A->B)해줄 것이다. 하지만, 현실적으로 웹서버에서 이러한 두가지 인코딩을 모두 명시하는 것은 상당한 복잡도를 야기시킨다. 또다른 문제는, ì´ëŸ° 웹서버로 Non-ASCII 문자를 포함한 URI를 생성해서 보내줄 때는 웹서버가 어떤 인코딩을 사용하는지 클라이언트 즉, 웹브라우저나 웹사용자가 인지하고 있어야한다는 것이다. URI가 웹페이지에서만 클라이언트에게 주어진다면 웹페이지의 인코딩을 자동적으로 따름으로서 이러한 문제를 해결할 수 있으나, URI의 interoperatibility를 크게 떨어뜨린다. í´ë¼ì´ì–¸íŠ¸ê°€ Non-ASCII 문자로 URI를 생성하는 일이 힘들어질 뿐만 아니라, 서버측의 문자 인코딩으로 인코딩된 URI를 클라이언트 측에서 디코딩해서 보여줄 수도 없게된다. (trivial한 클라이언트라면 서버측의 인코딩을 무시하고 클라이언트의 인코딩을 사용해서 디코딩함으로써 부분적으로 보여주는 시도를 할 수도 있을 것이다.) 

3번안의 경우에는 드디어 URI는 í´ë¼ì´ì–¸íŠ¸ì¸¡ì˜ 인코딩, 서버측의 인코딩과는 독립적이고, 클라이언트와 서버 모두 ì¶”가적인 정보없이 URI를 인코딩하고 디코딩할 수 있게된다. 따라서, 부가적인 설정이 필요하지도 않을 뿐더러, URI를 사용자에게 친숙한 형태로 디코딩해서 보여주기도 쉬울 것이다. 2번안에 비해서 크게 추가되는 비용도 없다.

One Character Encoding To Rule Them All

여기서 핵심은 RFC3986의 2.5절에서 언급하듯이, URI의 생성과 전송과정에서는 하나 이상의 문자인코딩이 관여할 수 있다는 것이다. 이를 해결하기 위한 가장 직관적인 방법들은 바로 ë¬¸ìž 인코딩을 명시하거나, 하나의 통일된 문자 인코딩을 정하는 것이다. 현재의 표준 명세상 URI에 문자 인코딩을 명시할 수 있는 메커니즘이 존재하지 않기 때문에 실현 가능한 것은 후자의 방식이고, 하나의 통일된 문자 인코딩은 바로 UTF-8이 되는 것이다. ê·¸ë¦¬ê³  그것이 3번안이 우리가 ë³´ê³  있는 해답인 이유다.

Internet Explorer 7 vs. mod_url

Internet Explorer ì˜µì…˜ì„ 세심하게 들여다보지 않았더라도, Internet Explorer가 URI들을 UTF-8로 인코딩하도록 하는 옵션이 존재한다는 사실은 한국인들에게는 잘 알려져있을 것이다. ì•„마도, 대부분의 사람들은 이 옵션을 꺼야 한국의 웹페이지들을 정상적으로 브라우징í•
  수 있었던 경험을 가지고 있을 것이다. 물론, 특히 URI에서 Non-ASCII 문자, 즉 한글을 사용하는 경우 말이다. 이유는 위에서 설명한 1번 방식이나 2번 방식에 해당하는 ì›¹ë¸Œë¼ìš°ì €, 웹서버들이 웹을 지배하고 있었기 때문이다. 적어도 Internet Explorer 5 부터 이 옵션이 있지만, Non-ASCII 문자를 포함하는 URI들을 위해서는 이 옵션을 끄도록 권장하고 있다.

내 기억으로는 이 옵션이 처음 등장했을 때는 (아마도 한글판에서만?) ê¸°ë³¸ì ìœ¼ë¡œ 꺼져있었던 것 같다. 그런데 언젠가부터인가 이 옵션이 기본적으로 켜지면서 많은 한국 사이트들에 문제가 발생했고 이 때 mod_url이 등장했다. mod_url은 UTF-8로 인코딩되어 웹서버로 들어오는 URI를 원하는 인코딩 (e.g. EUC-KR)로 변환하여 redirect 시킴으로써 웹 브라우저가 강제로 특정 인코딩을 사용하도록 만드는 일종의 트릭이었다. 이러한 방식이 Internet Explorer 6까지는 잘 동작했지만, Internet Explorer 7가 redirect된 URI도 UTF-8로 인코딩하게 ë˜ë©´ì„œ 이 트릭은 동작하지 않게 ë˜ì—ˆë‹¤. ê²°êµ­, mod_url을 사용해 문제를 해결했던 웹서버들에 다시 문제가 발생하게 되었다. (mod_url의 자세한 ë™ìž‘ 방식이나 IE 7ê³¼ 관련한 문제에 대해서는 mod_urlê³¼ IE7이라는 글과 ie7 utf-8 bug 한글주소 요청 버그라는 글을 참고하라.) Internet Explorer 7의 이러한 변경을 ‘버그’로 부르는 것은 잘못된 것이다. Internet Explorer 7은 좀 더 표준에 호환되는 조치를 취했을 뿐이고, 기존의 문제를 트릭으로 해결한 곳들만 문제가 발생한 것이다.

Internet Explorer 7 vs. Firefox

현재 Internet Explorer 7은 기본적으로 URI를 UTF-8로 Percent Encoding한다. Firefox는 Percent Encoding은 하지만, UTF-8로 하는 것이 아니라, 실행되는 환경의 기본 인코딩 (예를 들어, 한국어 Windows라면 EUC-KR, 현대의 리눅스 환경이라면 UTF-8)으로 Percent Encoding을 한다. 반면에, Percent Encoding을 하는 부분에서도 두 브라우저는 차이가 난다. Firefox는 URI 전체를 Percent Encoding하지만, Internet Explorer 7은 ? 이후의 부분 즉 query 부분은 Percent Encoding하지않고 그대로 보낸다. ê²°êµ­, 한글이 query에 포함되고, 이에 대한 인코딩을 EUC-KR로 가정하고 개발한 웹애플리케이션이 있다면, 리눅스에서 실행된 Firefox에서는 오동작할 거란 얘기다. 물론 두 브라우저 모두 애초에 제대로 인코딩 되어있는 URL은 그대로 처리하기 때문에, 이 문제를 피하기 위한 방법은 애초에 링크를 Percent Encoding하는 것이지만, 그런 정도로 신경을 쓴다면 애초에 이런 문제가 발생하지는 않았을 것이다. ê²°êµ­, Internet Explorer의 차기버전, IE8이나 IE9 ì¦ˆìŒì—ì„œ 이 동작도 표준 호환되게 변경되면 그제서야 사람들은 IE를 욕하면서 제대로 대처할 것이다.

Representing URIs as Decoded Form

사이트 내의 한글을 포함한 URI들을 UTF-8로 인코딩하는 작업을 하게되면 사용자들은 ê²°êµ­ 읽기 힘든 URI들만 보게 된다. Internet Explorer의 상태표시줄에는 디코딩된 형태의 URI를 보여주긴 하지만, 아무도 그곳은 보지 않는다. 만약 웹 브라우저들이 사람들이 URI를 ê°€ìž¥ 자주 접하는 ì£¼ì†Œí‘œì‹œì¤„에 디코딩된 형태의 URI를 보여준다면, 브라우징 경험은 상당히 향상될 것이다. http://ko.wikipedia.org/wiki/%EB%8C%80%ED%95%9C%EB%AF%BC%EA%B5%AD보다는 http://ko.wikipedia.org/wiki/대한민국이 좋지 않은가. 이미 구글과 같은 검색엔진에서는 검색 결과를 보여줄 때 디코딩된 형태로 보여주고 있다. (검색 ê²°ê³¼ anchor의 URI는 인코딩된 형태)

Closing

아직 I18N-aware하지 않는 시스템들이 많지만 개발자라면 ì´ì œë¶€í„°ë¼ë„ I18Nê³¼ 인코딩에 대해서 잘 이해하고 있어야한다. 문자 인코딩에 관한 이해가 부족한 개발자들을 너무나 자주 ë³¼ 수 있다. 상대방이 문자 인코딩에 관해 모른다면 어떻게 URI 인코딩에 대해 설명을 하겠는가. ê·¸ 사람은 과연 위의 문제들을 트릭 이외의 것으로 해결할 수 ìž
ˆì„까. 개발자에 있어서 의외로 언어나 라이브러리의 사용법은 중요하지 않다. 쉽게 배울 수 있기 때문이다. 언어나 라이브러리 ì¡°ì°¨ 혼자 공부하지 못한다면 개발자라는 직업은 그만두라고 하고 싶다. ì „에 언급했던대로 프로그래머에게 가장 중요하고, 교육 과정에서는 강조되면서도 쉽게 얻어지지는 않고, 또 인터뷰에서는 아무도 신경안쓰는 것은 바로 Abstraction에 관한 것들이다. 기본적인 개념들을 이해하지 못하고, 새로운 개념들을 만들어낼 수 없다면, 동작하는 것을 잘 만드는 인센티브를 많이 받는 개발자는 될 수 있어도, 다른 개발자들에게 인정받는 훌륭한 개발자는 될 수 없다. 물론 자기만족이 어디에서 나오는지에 관한 개개인의 선택이겠지만.

한편, URI 인코딩에 관한 여러가지 문제들을 ë³´ë©´ 웹 브라우저와 같은 중요한 애플리케이션은 제대로 동작하는 것도 중요하지만, 표준의 준수도 중요하다는 것을 느낄 수 있다. 비표준적인 형식을 한가지 허용할 때마다 얼마나 많은 사람들이 í˜¸í™˜ì„± 문제 때문에 골머리를 앓고 비용을 낭비했을까. 애초에 동작하는 버전을 만들 수 있었다면 아무 문제가 없었을텐데 말이다. 중요한 애플리케이션일 수록 표준 준수 여부는 면밀하게 관찰되어야하고 준수되지 않는 것은 비판되어야 마땅하다.

On Encoding URI with Non-ASCII characters 더 읽기"

Java SE 6 Released

Java SE 6가 릴리즈되었습니다.

Java 개발자라면 놓치면 안되는 몇가지 중요한 레퍼런스들을 살펴보시죠.

몇가지 코멘트.

Scripting

Agile 언어들의 생산성은 충분히 입증된 사실입니다. Java를 프로젝트의 주요 언어로 채택하더라도 각종 툴들에서 사용할 Agile 언어의 필요성은 여전히 남아있습니다. 그렇지 않아도 JRuby나 Groovy를 try해보려고 계속 생각 중이었는데, Agile 언어를 좀 더 잘 지원하게 되었다는 것은 반가운 소식입니다.

Database

Apache DerbySQLite를 대체할 수 있겠군요.

Performance

address resolving 상의 성능 문제 jrockit을 사용하고 있었는데 일반적으로 Java SE 5가 성능이 더 좋다는 것은 처음 알았습니다. (적절한 벤치마크를 해보지도 않았지만.) Java SE 6는 더 좋아졌다니 다행이군요. 현재 개발하고 있는 애플리케이션도 한번 벤치마크를 해보아야할 것 같습니다. 그나저나 Java SE 6에서는 제발 gethostbyname 대신 getaddrinfo를 사용해주었으면 좋겠군요. (소스도 한번 살펴보아야겠습니다.)

JDBC 4

SQL을 annotation으로 쓰는 기능은, annotation의 full power를 활용하는 동시에, Object-Relational impedance mismatch를 어느 정도 보완하려는 노력이 엿보이는 것 같습니다. JDBC를 사용하다가 발생하는 오류는 상황별로 자동 복구하는 등의 처리가 매우 힘들었는데, (그래서 기록해두는 수 밖에 없었는데), SQLException이 계층화된 것은 매우 환영할만한 일인 것 같습니다.

그나저나, JDBC 3과의 인터페이스 호환성이 없다는 것이 상당히 짜증스럽군요. (당연히 호환된다면 JDBC 4로 나올 이유도 없을 것 같긴 합니다만.) JDBC 3과 JDBC 4 둘다 지원하기 위한 유일한 방법은 빌드를 분리해서 바이너리를 나누는 수 밖에 없는 것 같습니다. 현재 진행중인 프로젝트라면 가능한 한 Java 6과 JDBC 4로 가는 것이 편하겠죠. 하지만, 안타깝게도 PostgreSQL의 JDBC driver 외에는 아직 공식적으로 JDBC 4를 지원하는 경우가 별로 없는 것 같습니다. 적어도 MySQL Connector/JCommons DBCP의 경우에는요. PostgreSQL의 경우에도 새로 추가된 메서드들을 제대로 구현하진 않았더군요. 소외받는 JDBC 4입니다.

Java SE 6 Released 더 읽기"

Anti-Pattern: Software Development Without Writing

Why We Write에서 쓰기의 중요성은 충분히 얘기한 것 같다. 소프트웨어 개발에서도 쓰기의 역할과 중요성은 다르지 않다. 하지만, 그 중요성을 인지하지 못하는 경우, 소프트웨어 개발에서는 여러가지 문제들이 발생할 수 있다. 이 글은 쓰지 않는 것이 소프트웨어 개발에 어떤 악영향을 미치는 지에 관한 개인적인 경험을 기술한다. 이 글은 소프트웨어 개발에 있어서 어떤 것을 써야하는가, 어떻게 써야하는가를 다루지는 않는다.

1. 명확하지 않은 명세(specification)

10페이지 내지 100페이지 짜리 Microsoft Word 명세 문서를 써내야한다고 주장하는 것은 아니다. 단 하나의 문장으로 쓰더라도, 명세는 글로 쓰여질 필요가 있다. 구두를 통한 명세와 그 전달은 애초에 명세가 불명확하거나, 그러한 불명확한 점을 쉽게 짚어낼 수 없고, 전달과정에서 왜곡되거나 손실되며, 이후에도 잘못된 점을 알아차리기 힘들다. 반대로, 명세가 글로 쓰여진다면, 처음부터 불명확할 가능성이 줄어들 것이며, 불명확함을 누구든 그것을 읽는 사람이 알아차리기 쉬울 것이며, 적어도 전달과정에서는 왜곡되거나 손실되지 않을 것이며, 나중에 다른 누군가가 보더라도 잘못된 점을 알아차릴 것이다.

2. 비효율적인 의사소통

구두를 통한 의사소통(직접 대면한 상태의 대화, 전화, 회의)의 커다란 단점 중의 하나는 바로 의사소통 참가자의 시간을 배타적으로 점유한다는 것이다. 의사소통이 언제 어디에서 일어날 것인지가 결정되면, 또는 이미 의사소통이 일어난 후에는, 다른 중요한 일이 있더라도 의사소통의 우선순위를 재조정하기는 매우 힘들다는 것이다. 쓰기를 통한 의사소통은 그렇지 않다. 의사소통의 참가자들은 자신이 원하는 시간과 장소를 사용할 수 있다. 우선순위의 조정이 매우 자유롭다. 물론, 구두를 통해서만 할 수 있는 활동(즉각적인 피드백, 상대방 의지의 확인 등)이 존재하기 때문에, 모든 의사소통이 쓰기를 통해야한다는 것은 아니다. 다만, 구두를 통해서만 할 수 있는 활동이 아닐 경우에는 쓰기를 선호해야한다는 것이다.

특정 시간과 장소에 국한된다는 구두를 통한 의사소통의 단점에서 파생되는 다른 문제는 바로 참여한 당사자 외에는 의사소통의 내용을 알기 힘들다는 것에 있다. 회의록을 남기는 이유는 바로 이러한 문제를 해결하기 위한 것이다. 공식적인 회의가 아니라고 하더라도 구두를 통한 의사소통을 쓰는 것은 같은 이유로 중요하다.

쓰기가 없는 구두를 통한 의사소통이 비효율적인 점을 보여주는 단편적인 예로, 한사람과 의사소통한 내용을, 다른 한 사람, 그리고 또 한 사람 이렇게 차례대로 의사소통하는 경우를 볼 수 있다. 그야말로 코미디가 아닌가.

3. 과거의 활동/결정에 대한 회고 불가능

쓰지 않는 조직해서 흔히 볼 수 있는 광경은 다음과 같은 것이다.

“이렇게 하기로 결정/생각/디자인했었는데, 실제로 반영/작업/구현을 했었던가?”

“대체 왜 우리가 그런 결정을 했더라?” “내가 왜 그렇게 작업/디자인/구현했지?”

한달 내지는 두달 만에 끝나고 다시는 쳐다보지 않을 소프트웨어 개발 프로젝트에서는 이러한 경우는 별로 없다. 하지만, 단기 프로젝트가 아니라면, 현대의 소프트웨어 개발의 특성상 환경과 요구사항은 빠르게 변화하기 때문에, 과거의 결정을 회고하고 다시 결정해야하는 경우가 자주 발생한다. 쓰지 않는 조직은 그 때마다 위와 같은 질문들을 하기 마련이다.

Anti-Pattern: Software Development Without Writing 더 읽기"

Why We Write

Introduction

쓰는 것의 가장 본질적인 이유 중의 하나는 인간의 기억력을 (일반적으로는 정신적인 능력을) 신뢰할 수 없고(unreliable), 기억은 (정신적인 활동은) 왜곡되기 쉽상(volatile)이기 때문이다. 문학의 경우에는 우리가 쓰는 것 자체나 또는 쓰는 것의 결과물을 읽음으로서 오는 어떤 종류의 카타르시스를 즐기기 위한 목적도 있을 수 있다. 소프트웨어 개발에 있어서는, 일반적으로 과학적인 연구나 엔지니어링에 있어서는 전자가 그 목적일 것이다.

쓰는 것은, 인간 능력의 한계라는 본질적인 한계에서 파생되는 문제들을 해결하고 있는데, 그 중의 가장 중요한 것들은 바로, 사고의 도구로서의 쓰기, 의사소통의 도구로서의 쓰기, 회고의 도구(기록)로서의 쓰기가 있다.

A Tool for Thinking

사고의 대상에 대한 기억이 없다면, 더 나아가서 기억력이 없다면 우리는 사고할 수 없다. 합리적인 사고의 과정은 일련의 사고 내용을 기억하는 단계를 포함한다. 대부분의 단순한 사고의 과정에서는 인간의 (한계를 가진) 기억력만으로도 충분하다. 하지만, 좀 더 복잡한 사고를 필요로 하는 경우에는 인간의 한계를 드러내기 시작해서, 사고의 과정은 끊겨버리거나, 잘못된 방향으로 나아가서 왜곡되어버리거나, 불충분 할 수 있다. 물론 그러한 한계는 개인적으로 다르고, 천재의 경우에는 그러한 어려움을 겪지 않을 수도 있으나, 일반적으로는 그렇지 않다.

쓰는 것은 이러한 어려움을 해결해주는 역할을 한다. 사고와 쓰기를 병행할 경우, 기억의 단속이나 왜곡을 막아주기 때문에, 우리는 사고의 대상과 내용, 방향을 정확히 유지할 수 있으며, 특히 과학적(또는 수학적) 사고에서 중요한, 모든 가능성을 타진하는 과정을 제대로 수행할 수 있다.

A Tool for Communication

의사소통 과정에서의 정보의 손실과 왜곡은 의사소통을 필요로 하는 모든 조직의 골칫거리다. 과학적인 연구 또는 엔지니어링에 있어서의 의사소통은 일반적으로 사고를 동반하므로 의사소통에서의 기억력이 차지하는 비중과 역할은 사고의 경우와 같다.

쓰기는 협업 사고의 도구로서만 의사소통에 작용하는 것이 아니라, 의사소통의 방식에도 관여해서 비동기적인 의사소통을 가능하게 해준다. 대부분의 구두를 통한 의사소통은 시간과 장소를 참가자들에게서 배타적으로 점유하는 동기적인 의사소통 방식이다. 동기적인 의사소통 방식은 시간과 장소를 공유하지 않는 사람은 의사소통에 참가할 수 없다는 단점을 내포한다. 물론, 참가자들의 의지를 확인할 수 있다거나 피드백이 빠르다는, 동기적인 의사소통 방식에 고유한 장점도 존재한다. 하지만, 장점만을 취할 수 없게 만드는 단점이 있기 때문에, 시간과 장소라는 자원이 부족한 현대인은 가능한 한 비동기적인 의사소통방식을 선호하는 것이 합리적이다.

A Tool for Retrospection (History, Record)

인간의 장기적인 기억력이 단기적인 기억력에 비해서 결코 더 믿을 수 없다는 것을 생각하면, 그리고 회고를 일종의 ‘장기간에 걸친 느린 사고’로 본다면, 회고에 있어서의 기억력의 비중과 역할은 사고의 경우와 같다고도 볼 수 있다.

하지만, 회고를 필요로 하는 종합적인 사고는 필요로 하는 기억들의 항목들이 더 많을 가능성이 높고, (기억들의 항목이 많으면 그들을 ‘모두’ 기억할 가능성이 낮아진다는 전제하에) 쓰기의 중요성은 사고의 도구로서의 경우보다 더 크다고 볼 수도 있다.

 

Conclusion

쓰기는 인간의 한계, 특히 기억력에 있어서의 한계를 보완하기 위한 중요한 도구다. 사고, 의사소통, 회고는 모두 이러한 인간의 한계가 제약하는 중요한 활동들이다. 쓰기는 이러한 활동을 효율적이고 정확하게 수행하는데 중요한 역할을 한다.

Why We Write 더 읽기"

Use FindBugs for Java Development

FindBugs 이클립스 플러그인은 정적 코드 분석 (static code analysis)를 통해 버그나 권장되지 않는 프랙티스들을 찾아주는 플러그인입니다. PMD, CheckStyle 등 몇가지 유사한 플러그인을 사용해보았는데, 어느 정도 설정을 조정해주어야 쓸만한 결과가 나와서, 이런 저런 튜닝을 하고 있는 중인데, FindBugs는 기본 설정으로도 결과가 좋아서, 고민없이 설치해서 써도 될 것 같습니다. PMD, CheckStyle 등을 잘 쓰는 것에 대해서는 다음에 써보겠습니다.

설치는 FindBugs update site ( http://findbugs.cs.umd.edu/eclipse )를 사용하시면 되구요. 이클립스의 프로젝트별 설정(Preference)에서 FindBugs를 활성화시켜주면 됩니다.

관련 레퍼런스는 The Last Mind 위키의 이클립스 플러그인 페이지를 참고하세요.

Use FindBugs for Java Development 더 읽기"

Behavior-Driven Development

내게 객체 기반 프로그래밍 (Object-Oriented Programming)를 한마디로 축약하라면, 행동(Behavior)을 기준으로 도메인을 나누는(decomposition)하는 프로그래밍 방식이라고 정의할 것이다. 다시 말해, 객체(Object)는 잘 정의된 행동(Behavior)을 대표해야한다는 것이다. 행동(Behavior)이 아니라 자료(Data)를 기준으로 디자인하면, 구조화 프로그래밍(Structured programming)의 수동적인 자료구조와 그것을 조작하는 함수들의 집합이 탄생할 뿐이다. 설령 그것이 클래스라는 프로그래밍 언어 상의 장치를 사용한다고 하더라도 객체 기반 프로그래밍이라고 부를 수는 없다. (이러한 밈(meme)에 대해서는 Object Thinking이라는 책을 참고하라.)

Behavior-Driven Development라는 말을 강문식 군에게 처음 들었을 때는 이러한 객체 기반 프로그래밍의 생각을 대변하는 일반적인 개념을 가진 말이라고 추측했는데, 알고보니 정체는 그 개념을 TDD에 적용한 좁은 의미의 것이었다. Dave Astels 자신이 정의한대로 Behavior-Driven Specification 같은 용어를 썼으면 좋았을텐데 하는 생각이 든다.

설령 그렇다고 하더라도, 행동을 기준으로 하는 디자인이 맨 먼저 테스트에 적용되는 것은 상당히 훌륭한 개념이라고 생각한다. TDD를 통해서 얻을 수 있는 이점인 재사용과 테스트가 쉬운 (Reusable and Testable) 프로그램을 만드는데 있어서 객체 기반 프로그래밍의 기본 개념인 행동(Behavior)을 기준으로 한 디자인까지 할 수 있다면, 더할 나위 없이 좋을 것이다. 다만, 그것이 테스트 또는 스펙에만 머무르지 않고 실제 행동(Behavior)을 나타내는 디자인에까지 적용되어야 하겠지만 말이다.

아래는 Dave AstelsBDD에 대한 Google TechTalks 강연 내용 요약.

Unit vs. Behavior

unit: isolated focus like class, method
behavior: little fine-grained focused picses of behavior

unit should replaced by behavior
test should replaced by specification
assertion should replaced by expectation

rSpec

xunit should replaced by rSpec
assert_equal(expected, actual)->actual.should.equal expected

The Expectation API

  • equality
    • e.g. should.equal/sould.not.equal
  • counts
    • should.have(5).items
    • should.have.at.least(5).items
    • should.have.at.most(5).items
  • arbitrary block
    • should.satisfy { |obj| … }
    • should.not.satisfy { |obj| … }
  • pattern matching
    • should.match
  • arbitrary predicate
    • should.predicate (predicate? is defined on the target)
  • forced failure
    • violated(message)
  • exception
    • should.raise <exception>
  • direct class
    • should.be.an.instance.of C
  • ancestor class
    • should.be.a.kind.of C
  • interface
    • should.response.to :message

The Mocking API

just like jMock

  • creating a mock
    • m = mock(“mock name”)
  • expecting a method
    • should_receive(:name)
  • counts
    • never/once/twice/at_least_once/any_number_of_times
  • Arguments
    • with_no_args
    • with_any_args
    • with(arg1, arg2, …)
  • Return values
    • and_returns(value)
    • and_returns_consecutively([…])
  • Provide a Block
    • should_receive(:name) {
    • |arg1, args2, …|

Why Ruby?

  • Dynamic
  • Productive
  • Fun

Behavior-Driven Development 더 읽기"

RubyOnRails로 30분만에 정책 관리자 도구 만들기

회사에서 하고 있는 프로젝트에서 정책 데이터들을 MySQL 데이터베이스로 관리하고 있는데요. 보편적인 MySQL 관리툴인 phpMyAdmin 조차 사용하지 않고 그냥 손으로 관리하고 있길래 RubyOnRails로 간단한 관리 도구를 만들어보았습니다. 정확히 30분 걸렸습니다. 많은 관리 도구들이 CRUD 정도만을 필요로 하다는 것을 고려하면 RubyOnRails의 scaffolding만으로도 거의 충분하다고 볼 수 있죠. 다음에 시간이 나면 authentication/authorization 정도를 붙여볼 생각입니다.

Setting up database configuration

$ vi config/database.yml

development:
  adapter: mysql
  database: scheduler_policy_manager_development
  username: ***
  password: ***
  host: ***

Creating a model

$ ruby script/generate model address_schedule_policy

Creating a table for the model

$ vi db/migrate/001_create_address_schedule_policies.rb

class CreateAddressSchedulePolicies < ActiveRecord::Migration
  def self.up
    create_table :address_schedule_policies do |t|
        t.column
:name,     :string
        t.column
:ip,       :int
        t.column :period,   :int
    end
  end

  def self.down
    drop_table :address_schedule_policies
  end
end

$ rake db:migrate

Creating a scaffolding controller

$ ruby script/generate controller admin
$ vi app/controllers/admin_controller.rb

class AdminController < ApplicationController
    scaffold :address_schedule_policy
end

Running the web server

$ ruby script/server

=> Booting WEBrick…
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with –help for options
[2006-10-09 20:58:38] INFO  WEBrick 1.3.1
[2006-10-09 20:58:38] INFO  ruby 1.8.4 (2005-12-24) [i486-linux]
[2006-10-09 20:58:38] INFO  WEBrick::HTTPServer#start: pid=26079 port=3000

Adding some validation

$ vi app/models/address_schedule_policy.rb

class AddressSchedulePolicy < ActiveRecord::Base
    validates_presence_of :name, :ip, :period

    protected
    def validate
        errors.add(:period, “should be at
least 5000 (ms)”) if period.nil? || period < 5000    end
end

Generating scaffold

$ ruby script/generate scaffold address_schedule_policy admin

Making a small change to the table

$ script/generate migration ChangeTypeOfIpField
$ vi db/migrate/002_change_type_of_ip_field.rb

class ChangeTypeOfIpField < ActiveRecord::Migration
    def self.up
        change_column
:address_schedule_policies, :ip, :string
    end

    def self.down
        change_column
:address_schedule_policies, :ip, :int
    end
end

$ rake db:migrate

Installing streamlined generator

$ cd ~/tmp
$ wget http://streamlined.relevancellc.com/streamlined_generator-0.0.5.gem
$ sudo gem install streamlined_generator-0.0.5.gem

Generating streamlined scaffold for the model

$ ruby script/generate streamlined address_schedule_policy
# should restart WebRick to show streamlined correctly
$ script/server

Installing mongrel (the alternate web server)

$ sudo gem install mongrel

Running the new web server

$ mongrel_rails start

RubyOnRails로 30분만에 정책 관리자 도구 만들기 더 읽기"

Java Identifiers in Unicode

Java Identifiers in Unicode

요즘 The Java Programming Language를 읽고 있는데, Java는 identifier들을 Unicode로 쓸 수 있다는 점이 신기해서 만들어본 코드.

public class 메롱이 {

public void 말한다() {
System.out.println("메롱~");
}

public static void main(String [] 인자들) {
메롱이 메롱메롱이 = new 메롱이();
메롱메롱이.말한다();
}
}

Java Identifiers in Unicode 더 읽기"

Prefer Multithreading to Event-driven in Highly-Concurrent Servers

Introduction

‘서버를 만들 때 Multithreading을 사용해야하는가 아니면 Event-driven (흔히, select/poll/epoll/kqueue 사용한) 방식을 사용해야하는가’라는 질문을 자주 받는다. 대략 내가 처음 job으로서의 프로그래밍을 시작하던 2001년에만 하더라도 리눅스를 포함한 POSIX platform에서의 쓰레드 구현은 형편없었고, select나 kqueue 등을 사용하는 것이 High Performance, High Concurrency를 위한 서버를 만들기 위한, 다시 말해 C10K 문제를 해결하기 위한 거의 유일한 방법이었다. 하지만, 컴퓨팅 환경은 (언제나처럼) 급속도로 발전했고, Multithreading을 사용해 몇몇 프로덕트를 만드는데에 성공하면서, 2004년 말 정도에는 Event-driven보다 Multithreading이 더 나은 해법이 아닌가하는 의심이 들 정도였다. 이러한 의심으로부터 Multithreading이 우월한 해법이라는 것을 확신시켜준 것은 태준옹 와의 대화였고, 이후로는 위와 같은 질문에 대해서는 항상 ‘굉장히 performance-critical한 서버가 아니라면 multithreading을 사용하는 것이 좋다‘라고 답변하고 있다. 그리고, ‘굉장히 performance-critical한 서버가 아니라면’이라는 가정조차도 점점 힘을 잃어가고 있는 추세이다.

그러던 와중에, CN님이 2005년 4월 30일에 쓰신 ‘프로그래머에게 해로운 두가지 1: 쓰래드’라는 글로 Multithreading에 비해 Event-driven을 선호해야한다는 글을 쓰셨는데, 해당 글의 코멘트에서 논쟁을 벌이다가 Multithreading을 방어하기 위한 글을 따로 써야겠다는 마음을 먹었으나, 이를 1년 6개월 만에 실행에 옮기게 된 것이 바로 이 글이다.

Performance

경험 있는 서버 개발자들이 주로 Multithreading에 비해 Event-driven을 선호하는 가장 큰 이유는 performance일 것이다. 실제로 옛날에는 Event-driven 방식을 사용하는 것이 상대적으로 엄청나게 뛰어난 performance를 보여주었다. 하지만, 어디까지나 그건 ‘옛날 이야기’다. 이렇게 된 이유는,

  • OS의 Multithreading 지원과 이를 통한 더 나은 쓰레드 구현
  • CPU 속도의 증가 경향
  • Multiprocessor의 보편화 경향(CPU 수의 증가 경향)과 OS의 지원

정도로 들 수 있을 것 같다.

Multithreading을 사용할 때 performance에 대해 걱정하는 점들은 더이상 문제가 되지 않는다. 하나씩 짚어보면,

  1. 쓰레드가 많아지면 schedule하는데 부담이 크지 않을까?
    • Linux 2.6 커널은 O(1) 알고리즘의 scheduler를 가지고 있다. (Solaris나 Windows에서도 반영된다고 했으나, 현재 상태는 알 수 없다.) 쓰레드 수가 많다고 해서 scheduling overhead가 증가하는 일은 없는 것이다. 또한, 대화하다보면 sleeping 상태의 쓰레드도 scheduling 오버헤드에 포함될 것이라고 생각하는 사람도 많이 있는 것 같은데, 적어도 Linux에서는 그렇지 않다.
  2. 쓰레드가 많아지면 context switching cost가 많이 들지 않을까?
    • 현대의 architecture, 적어도 x86은 cpu context가 그다지 크지 않게(몇몇 register를 메모리에 save/restore하는 정도로) 설계되어있다. 실용성은 떨어지지만 32bit x86은 thread에 대한 CPU 레벨의 지원도 포함하고 있을 정도다. (물론, Linux에서는 사용하지 않는다.) 더군다나 CPU 속도가 빨라지면서 context switching의 비용은 점점 중요하지 않게될 것이다. P4 3GHz 머신에서 lmbench의 결과로는 2-3 microseconds 정도의 context switch latency가 있을 뿐이다.

여기에 Multiprocessor 문제가 추가되면 더더욱 Multithreading의 손을 들어줄 수 밖에 없다. Consumer PC에서도 Multiprocessor가 일반화되어가는 추세라는 것은 대부분의 사람들이 이제는 동의할 것이다. 프로그래머가 user-level에서 어떤 Multiprocessor를 사용할 것인가, 또는 각 processor에 연결된 메모리를 어떻게 효율적으로 사용할 수 있는가(NUMA )와 같은 문제까지 신경쓸 수는 없다. 결국, Multiprocessor를 효율적으로 사용하기 위해서는 processor 자원의 분배를 OS에 의존하는 Multithreading이 가장 좋은 방법이 되었다. 최근 수년간 (특히, Linux/FreeBSD에서는) Multiprocessor를 지원하기 위해 OS 디자인에서도 변화가 생기는 등 여러가지 노력들이 보이고 있다.

Event-driven 방식은 원래 하나의 쓰레드에서 event dispatcher와 handler가 함께 동작하는 방식이었으나, Multiprocessor를 활용하기 위해서는 event dispatcher를 processor 수에 따라 나누던가, handler가 쓰레드 풀 상에서 동작하도록 하는 방식을 채용하는 수 밖에 없다. 이렇게 되면, Event-driven 방식을 주장하는 사람들이 좋아하지 않는 쓰레드의 온갖 단점들이 Event-driven 방식에도 도입되게 된다. 그럼에도 불구하고 Multithreading이 Event-driven에 비해 state의 효율적인 관리가 불가능(이를테면, idle connection을 handle하기 위한 thread의 불필요한 stack 차지)하다는 주장을 할 수 있으나, 이 문제는 다음 섹션을 보도록 하자.

State Management

최근에 이루어진 컴퓨팅 환경의 변화 중 또다른 하나의 커다란 축은 바로 64bit 주소 공간의 도입일 것이다. 일단 주소 공간이 충분히 넓어졌기 때문에 쓰레드 수에 따른 주소 공간의 낭비는 크게 의미가 없게 되었다. Linux와 같은 현대의 OS는 스택에서도 페이지 폴트가 발생할 때 물리 메모리를 할당하는 방식을 취하므로 물리 메모리의 낭비 또한 거의 없다. 설령 32bit 머신을 사용하고 있어서 주소 공간이 부족하다고 하더라도, Linux의 쓰레드 구현인 NPTL은 쓰레드별로 스택 크기를 조정할 수 있으며, 최악의 경우에는 Event-driven과 같이 대부분의 state를 heap에 두면 대부분의 문제를 해결할 수 있다.

Synchronization

대부분의 서버의 경우 connection 단위의 세션들이 서로 공유해야하는 것이 별로 없는 경우가 많기 때문에 Synchronization 이슈가 생각만큼 크지는 않다. 하지만, 분명히 복잡한 logic을 가지고 synchronization을 많이 사용해야하는 경우도 존재할 것이다.

Event-driven 방식에서는 Synchronization의 문제가 자동적으로(for free) 해결된다고 하지만, 이는 Uniprocessor라는 가정하에서 뿐이다. Multiprocessor가 보편화된 환경을 고려하자면 Event-driven 방식이라고 해서 Synchronization 문제를 해결하지 않는 것은 어리석은 일이다. 더구나 Event-driven 방식에서 Synchronization을 하기 위해서는 state가 적어도 하나 더 추가되어야 한다. 아래에서도 설명하겠지만, Event-driven 방식에서 state 하나가 추가되는 것은 state 수 이상의 복잡도를 발생시킨다. 결국은 Synchronization을 하지 않는 디자인을 하는 수 밖에 없고, 이것은 말그대로 Synchronization을 하지 않는 것 뿐이고, 이러한 디자인 하에서는 Multithreading 방식으로 구현하더라도 Synchronization이 필요 없음을 의미할 뿐이다.

Why Events Are A Bad Idea에서 지적되었던 것처럼 Event-driven 방식 자체가 Synchronization 이슈를 해결하는 것이 아니라는 점을 되새길 필요가 있다. Multithreading에서 복잡한 Synchronization이 필요하다면 Event-driven 방식에서도 마찬가지다.

Complexity

Event-driven 방식 사용을 자제해야하는 가장 커다란 이유는 Complexity에 있다. Multithreading에 반대하는 많은 사람들이 Synchronization의 복잡도 때문에 Event-driven 방식을 사용해야한다고 주장하지만, Event-driven 방식을 제대로 경험해보지 못한 사람의 생각이라고 얘기하고 싶다.

2001년에 나는 당시로서는 서버 프로그래밍 방법 중 최고의 성능을 보여준다고 하는 kqueue()를 사용해서 Web Cache내의 Event-driven 방식 서버 엔진을 만든 경험을 가지고 있다. Web Cache는 단순한 서버가 아니라 서버와 클라이언트를 모두 포함하고 있기 때문에, 비교적 복잡한 Network I/O 패턴을 가지고 있다. 여기에 ICP(Internet Cache Protocol)나 DNS resolving과 같은 또다른 Network I/O나 cache 페이지를 접근하기 위해 Disk I/O가 더해지면 state 수가 불어나면서 엄청난 복잡도를 가지고 온다. Dispatcher와 State/Session 관리 부분을 모두 프레임웍화 한다고 하더라도 Event-driven 방식 자체에 내재한 복잡도는 state가 하나씩 추가될 때마다 state의 수 이상으로 증가한다. 다행히, 프로덕트는 나왔고, 외국의 벤치마크에서 좋은 평가를 받고, 상용화도 되었고, 어느 정도 팔렸으나, 차후에 자체적으로 개발한 쓰레드 패키지를 이용한 쓰레드 방식으로 재개발 되었다. 그 정도로 Event-driven 방식으로 만들어진 코드의 관리는 지속 불가능했다는 것이다.

이 후에, 다른 회사로 옮겨간 이후에도 서버 프로그래밍을 계속 해오면서 느낀 점 중 하나는, 많은 프로그래머들은 Event-driven 방식을 이해하기 힘들어 한다는 것이었다. 이것이 Event-driven 방식에 대한 회의를 느끼게 된 커다란 이유 중의 하나가 되었다. 나름대로 내린 결론은 기본적으로는 Why Events Are A Bad Idea에서도 지적되었던대로 Non-linearity다. 클라이언트-서버 패턴에서 서버는 클라이언트로부터의 요청을 처리해서 답변을 되돌려준다는 linear한 개념을 기반으로 하기 때문에, Event-driven이 서버에 도입하는 non-linearity는 클라이언트-서버 패턴을 구현하는데 중대한 장벽이 된다.

이 문제를 좀 더 자세히 들여다보면, decomposition 문제로 치환해서 생각해볼 수도 있다. 일반적으로 좋은 프로그램들은 logical한 단위의 decomposition을 사용한다. 그것이 structured이든 object-oriented이든 말이다. Event-driven 방식은 이벤트의 발생이 decomposition의 단위가 된다. 이벤트가 특정 애플리케이션에서 중요한 의미를 가지고 있다면 별다른 문제가 되지 않는다. 하지만, 적어도 서버에서는 일반적으로 이러한 이벤트의 발생이 자연스러운 기준이 되기는 힘들다. Event-driven 방식의 프로그래밍을 직접 해보면 깨닫게 되지만, 뭔가를 기다려야하는 operation, 즉 blocking operation이 도입될 때마다, 기다리는 상태와 깨어난 상태를 나타내는 두개의 state와 기다리는 상태에서 벗어나기 위한 이벤트가 하나씩 추가되어야 한다. 서버에서 이러한 blocking operation의 전형적이고 가장 자주 나타나는 예는 모든 종류의 I/O (Network I/O, Disk I/O, …)라고 볼 수 있다. 여기서 질문을 하나 던진다면, I/O 이벤트가 과연 좋은 decomposition 단위일까? 서버에서 I/O가 얼마나 높은 수준의 의미를 가질까? 이러한 blocking operation에 I/O만 존재한다면 그나마 행복한 편이다. Timeout을 상상해보라. Lock, Condition variable과 같은 Synchronization을 Event-driven 방식에서 구현한다고 상상해보라. (위에서 언급했듯이 Event-driven이라고 하더라도 Multiprocessor에서는 Synchronization이 필요하다.) I/O할 곳이 추가될 때마다, Synchronization이 추가될 때마다 서버의 복잡도는 지수적으로 증가할 것이다. 얼마지나지 않아 그 서버는 본인도 알아보기 힘들고, 아무도 건드리려하지 않는 괴물이 될 뿐이다. Web Cache를 만들던 시절에는 이러한 단점을 보완하기 위해서 I/O를 동반하지 않은 state를 만들거나 substate를 만들기도 했으나, 이러한 시도는 Why Events Are A Bad Idea에 언급되었던 ‘Just Fixing Events’에 지나지 않는다.

물론, Event-driven 방식이 완전히 쓸모없는 decomposition 방식인 것은 아니다. 애플리케이션 특성 상, 이벤트가 높은 수준의 abstraction을 가지는 경우, 특히 GUI 애플리케이션과 같이 어떤 이벤트가 일어났을 때 어떤 작업을 해야하는가를 생각하는 것이 자연스러운 경우가 분명히 존재한다.

Idle Connection Management

Introduction에서 했던 답변에 항상 덧붙여서 하는 얘기 중 하나는 바로 idle connection의 관리에 관한 얘기다. 서버를 Multithreading으로 구현한다고 하더라도 idle connection과 같이 하나의 thread를 할당하기가 불필요한 정도는 Event-driven 방식으로 따로 관리해서 thread의 낭비를 막는 방법 정도는 사용해볼만 할 것이다. Multithreading을 선호하라고 해서, 무조건 Event-driven 방식을 쓰지말라는 얘기는 아니라는 얘기다.

Cooperative Threads

Why Events Are A Bad Idea에서는 Synchronization이나 Scheduling에서의 오버헤드를 방지하기 위해서 User-level의 Cooperative Threads 구현을 사용해서 해결책을 내놓고 있지만, 위에서 기술한대로 Kernel-level 쓰레드 구현을 사용하더라도 크게 문제가 없거나 오히려 더 나은 해결책이 될 수 있다.

Standard Thread API

Linux에서도 기존의 구현 (linuxthreads)은 signal과 관련된 동작들이 제대로 정의된 최근의 POSIX 표준을 따르지 않았기 때문에 여러가지 문제들이 많았다. 특히, 커널의 지원이 미비한 상황에서 구현의 한계상 signal과 관련한 표준들이 제대로 지켜지기 힘들었다. 하지만, Linux 2.6에서 지원되는 NPTL은 구현시부터 POSIX compliance가 상당히 중요한 목표로 책정되어 개발되었다.

Conclusions

기존에는 Event-driven방식이 Highly-Concurrent 서버를 개발하기 위한 최적의 방법이라고 생각되었으나, 이제는 Multiprocessor의 보편화, 저가의 메모리, 64bit 메모리 스페이스, OS와 쓰레드 구현의 진화 등 컴퓨팅 환경의 변화로 인해서 Event-driven방식 보다는 Multithreading 방식을 선호하는 것이 타당하다고 생각된다.

Prefer Multithreading to Event-driven in Highly-Concurrent Servers 더 읽기"