2014년 7월 4일 금요일

실시간 트위터 이미지 사이트 개발기 3


웹소켓(WebSocket)

웹 브라우저에서 오랫동안 사용한 통신 프로토콜은 HTTP다. HTTP는 동기 방식의 요청-응답 프로토콜로 반드시 클라이언트인 웹 브라우저가 요청해야만 서버인 웹 서버가 응답하는 프로토콜이다. 그러므로 HTTP 접속은 요청으로 시작해서 요청-응답을 반복하다가 반드시 응답으로 끝난다. 이 방식은 최초에 웹 혁명을 이끄는 데 있어서 중요한 단순성을 제공했다. 단순한 요청-응답 방식은 극단적인 수준까지 요청-응답을 처리하도록 서버를 개선하도록 했다. 범용 웹 사이트를 가능하게 하기 위해 TCP/IP 스택은 비약적으로 개선됐고, 아파치 웹 서버 같은 견고하고 성능도 좋은 서버들도 개발됐다. 사실 웹 기술이 보급되기 전까지 전통적인 클라이언트 서버 방식의 서버는 수십 클라이언트들의 접속을 처리할 수 있다면 할 일을 충분히 한다고 인정받던 시절도 있었다. 그러나 웹의 보급과 발전 덕분에 서버는 동시에 수천 접속을 유지하고 수만 요청도 처리할 수 있도록 비약적으로 진화했다. C10k 문제(일만 동시 접속 문제)라는 용어가 등장할 정도로 다수 동시 접속 처리 문제는 개발자들을 골치 아프게 했지만, 현재는 다양한 아키텍처 패턴을 이용해 이 문제를 극복하고 있다. 그러나 시대가 발전하면서 요청-응답으로는 처리하기 어려운 시나리오들도 웹 브라우저를 이용해 처리하고자 하는 요구가 끊임없이 확대됐다. 예를 들어 웹에서 실시간으로 변하는 증권 시세를 표시하려면 요청-응답 방식만으로는 해결할 수 없었다. 해결했다손 치더라도 속도나 성능 효용성이 충분하지 못했다. 그래서 자바 애플릿이나 액티브X 컨트롤을 이용해 소켓 통신으로 문제를 우회하려고도 했다. 그러나 이런 플러그인 기술들도 만족스럽지 못했고 보안 취약성이 또 다른 문제가 되기도 했다. 또 다른 예로 웹 채팅 애플리케이션이 있다. 웹 채팅을 구현하려면 Ajax 같은 복잡한 기술을 사용해야 그런대로 가능했다. 이런 방법들은 온전한 비동기 통신 기술이 아니다. 채팅은 상대방이 입력한 글이 웹 브라우저의 요청 없이도 즉시 본인의 웹 브라우저에 도착해야 하는 전형적인 비동기 통신 모델이다. 그런데 Ajax도 반드시 요청해야만 응답을 (비동기로) 수신할 수 있으므로 온전한 비동기 통신 방식은 아니다. 결국 요청-응답 방식의 HTTP 프로토콜 덕에 가질 수 있었던 단순성이 웹의 성공을 이끌었던 반면, 이 단순성은 웹 기반 비즈니스 시나리오가 확대됨에 따라 점점 부족함으로 작용하게 됐다. 완전한 비동기를 웹 브라우저에서 사용하려고 할 때, 단순함이 도리어 복잡한 Ajax 방식을 사용하게 강요한 면도 있었다. 이와 같이 확장되는 웹 기반 비즈니스 시나리오를 충족시키기 위해서는 좀더 편리한 통신 방법이 필요했다. 그리고 그 산물로 완전한 양방향 통신을 지원하는 웹소켓 프로토콜이 등장했다.

웹소켓 프로토콜은 상당히 최근에 등장했다. 2011년에서야 IETF는 웹소켓 프로토콜을 정의한 RFC 6455 문서를 표준으로 제정했다. 이에 따라 웹 브라우저들은 2011년부터 상호 호환성이 보장되는 RFC 6455 웹소켓 표준으로 웹소켓 지원을 통일했다 크롬 16, 사파리 6, 파이어폭스 11, 인터넷 익스플로러 10 이후 웹 브라우저들은 RFC 6455 웹소켓 표준을 준수한다. 웹소켓은 글자 그대로 소켓과 거의 같은 기능을 제공한다. 즉 일반적인 소켓이 제공하는 양방향 통신을 제공한다. (그러나 TCP/IP 계층보다 상위 계층 프로토콜로 독자적인 패킷 구조를 갖는다.) 그러므로 HTTP 프로토콜과 달리 접속이 완성된 후 TCP 소켓 접속처럼 서버의 우선 전송도 가능해 진다. 즉 요청-응답 순서가 클라이언트에서 서버뿐만 아니라 서버에서 클라이언트로도 가능하다. 그러므로 웹소켓은 클라이언트 풀(pull) 뿐만 아니라 서버의 푸시(push)도 어렵지 않게 제공한다. 그 결과 웹소켓은 동기 방식뿐만 아니라 비동기 방식 통신 프로토콜로 완벽하게 구현하게 하는 기반을 제공한다.

웹소켓은 HTTP 프로토콜에서 프로토콜 갱신 핸드쉐이크를 통해 웹소켓 프로토콜로 전환이 되는 구조를 가지고 있어, HTTP 표준 포트인 TCP 80 포트를 그대로 활용할 수 있다는 장점을 가진다고 한다. 그러나 이 부분에 있어서 필자는 다른 견해를 갖는데, 그 이유는 TCP 80 포트를 바인드하는 웹 서버가 HTTP 프로토콜과 웹소켓 프로토콜을 동시에 처리하려면 내부 아키텍처가 상당히 다른 두 프로토콜 스택을 서버 안에 함께 포함해야 한다. 웹소켓은 Stomp 같은 상위 프로토콜의 하부 프로토콜로 사용되기도 하는데 이 경우 또 다른 Stomp 프로토콜 스택도 웹 서버에 포함시켜야 하는 문제가 발생한다. 프로토콜 스택 개발은 웹 서버 개발과는 다른 문제일 수 있다. 그리고 아직 웹 서버들이 충분이 웹소켓을 다루는 기술이 성숙되지 않은 문제도 있다. 그러므로 웹소켓을 사용해야 한다면 웹 서버와 별도로 전용 웹소켓 서버를 운영하면서 TCP 포트를 별도로 개방하거나 방화벽 등의 정책으로 인해 접속 문제가 있을 수 있다면 별도 서버에 또 다른 TCP 80 포트를 개방해 웹소켓 서버를 운영하는 방법이 합리적이지 않을까 생각된다. 아직까지는 웹소켓의 사용 방식에 대해 뚜렷한 일관적인 방식이 등장하고 있지는 않은 것 같다.

웹소켓의 또 다른 문제는 웹소켓 위의 애플리케이션 프로토콜이 충분히 성숙돼지 않았다는 점이다. 웹소켓은 TCP 계층에 아주 얇은 층을 추가한다. 그러므로 웹소켓을 직접 사용하는 것은 마치 TCP 소켓을 직접 사용하는 것과 같다. 일반적으로 개발자들은 TCP 소켓을 직접 사용하기보다 상위 애플리케이션 프로토콜을 이용한다. 이런 예로 Telnet, Ftp, Ssh, HTTP, AMQP, OpenWire, Stomp, MQTT 등등이 애플리케이션 프로토콜이다. 그러므로 웹소켓을 충분히 잘 활용하려면 웹소켓 위의 애플리케이션 프로토콜이 많아지고 또 쓰임새도 정비돼야 한다.


실시간 트위터 이미지 사이트의 웹소켓 사용

웹소켓을 먼저 설명한 이유는 실시간 트위터 이미지 사이트도 웹소켓을 사용하기 때문이다. 실시간 트위터 이미지 사이트는 웹소켓을 아주 쓸모 있게 사용한다. 실시간 트위터 이미지 사이트의 전체적인 정보 흐름과 통신 프로토콜들은 다음과 같다.

1) 미국에 위치한 실시간 트위터 이미지 사이트의 백그라운드 Camel 서비스는 트위터에서 고양이로 트윗을 검색해 결과들을 수신한다. 2) 그 중 이미지를 포함한 트윗을 추출해 한국에 위치한 ActiveMQ 서버에 OpenWire 프로토콜을 사용해 메시지로 전달한다. 3) 메시지를 수신한 ActiveMQ 서버는 메시지를 게시 구독 방식의 토픽에 게시한다. 실시간 트위터 이미지 사이트에서 페이지를 조회한 웹 브라우저는 이미 웹소켓 위의 Stomp 프로토콜로 ActiveMQ 서버에 접속한 토픽 구독자다. 4) 토픽에 게시된 고양이 이미지 트윗 메시지 사본은 웹소켓 위의 Stomp 프로토콜을 통해 ActiveMQ 서버로부터 웹 브라우저로 비동기적으로 전송된다. 웹 브라우저는 비동기적으로 수신된 메시지를 웹 브라우저 화면에 고양이 쎔네일 이미지로 출력한다.

미국에 위치한 실시간 트위터 이미지 사이트 웹 서버의 Camel 서비스와 대한민국에 위치한 ActiveMQ 서버는 메시징 프로토콜로 OpenWire 프로토콜을 사용했다. OpenWire 프로토콜은 다양한 언어나 플랫폼에서 ActiveMQ에 접속할 수 있게 하는 메시징 프로토콜이다. ActiveMQ는 OpenWire, AMQP, XMPP, MQTT 등 다양한 메시징 프로토콜을 지원한다. 웹 브라우저와 ActiveMQ 서버는 웹소켓 위의 Stomp 프로토콜을 사용한다. Stomp 프로토콜을 사용한 이유는 WebSocket 위의 Stomp 프로토콜을 지원하는 STOMP.js라는 JavaScript 라이브러리가 있고, ActiveMQ 서버도 웹소켓 위의 Stomp 프로토콜을 잘 지원하기 때문이다. 참고로 TCP/IP 위의 Stomp 프로토콜과 웹소켓 위의 Stomp 프로토콜은 호환되지 않는다. TCP/IP 위의 Stomp 프로토콜은 Stomp 프로토콜이 TCP 계층 바로 위에 구현되는 반면, 웹소켓 위의 Stomp 프로토콜은 TCP 계층 위에 웹소켓 계층이 있고 그 위에 Stomp 프로토콜이 구현된다. 그러므로 Stomp 프로토콜을 지원한다고 해도 직접적으로 웹소켓 위의 Stomp 프로토콜로 통신할 수 없다. 문자 기반 프로토콜이어서 인지, 웹소켓 위에서 가장 구현하기 쉬워서 인지 상당히 이른 시기에 웹소켓 위의 Stomp 프로토콜은 등장했다. 한편 RabbitMQ의 기본 프로토콜인 AMQP 프로토콜이 웹소켓 위의 JavaScript 라이브러리로 개발이 된다면 웹 브라우저에서도 AMQP 프로토콜을 사용할 수 있을 것이다. 그러나 현재 RabbitMQ 쪽에서는 Node.js와 연결에 주력하고 있는 것 같다. RabbitMQ는 Node.js에서 동작하는 AMQP JavaScript 라이브러리까지만 지원하고 있다. 아마도 이 JavaScript 라이브러리는 TCP/IP 위에서 구현된 라이브러리일 것이다. 즉 AMQP 프로토콜의 하부 통신 계층이 TCP일 것이다. 그러므로 현재 웹소켓 위에서 사용할 수 있는 메시징 프로토콜 라이브러리는 STOMP.js이 유일하다. (글을 쓰면서 좀더 조사해 보니 최근 들어 웹소켓 위의 MQTT 프로토콜도 사용 가능한 것으로 보인다. 이에 대한 검토는 독자들에게 맡긴다!) 여기서 주목할 점은 메시징 프로토콜과 메시지 채널(큐, 토픽)은 서로 다른 종류의 개체라는 점이다. 즉 같은 메시지 채널이라도 여러 프로토콜로 발신, 수신, 게시, 구독이 가능하다. 실시간 트위터 이미지 사이트도 동일한 토픽에 대해 메시지 전송은 OpenWire 프로토콜을 메시지 수신은 웹소켓 위의 Stomp 프로토콜을 사용했다.

웹소켓을 사용하면 웹 브라우저를 기업 통합 솔루션의 비동기 메시징 컴포넌트의 일원으로 포함시킬 수 있게 된다. 실시간 트위터 이미지 사이트는 트위터 이미지 메시지를 비동기로 전송하는 용도로 간단하게 웹소켓을 사용했지만, 앞서 언급했듯이 웹소켓은 웹 브라우저의 요청-응답 또는 폴링 방법으로는 해결하기 어려운 실시간 비동기 정보를 손쉽게 처리할 수 있게 해준다. 예를 들어 실시간 뉴스, 실시간 대시보드, 실시간 제어, 실시간 게임, 실시간 그래프 애플리케이션 같은 것들도 웹 브라우저로 구현할 수 있는 가능성을 열어준다. 이를 위해서는 SVG(SVG(Scalable Vector Graphics)나 Canvas 같은 HTML5의 다른 기능들도 활용해야 할 것이다. 또한 필자도 웹소켓이 요청-응답 형식의 기존 HTTP 프로토콜을 완전히 대체할 수 있다고는 생각지 않는다. 기존 HTTP 프로토콜은 화면 조회, 웹 서비스, Restful, Ajax RPC 등 다양한 요청-응답 패턴을 여전히 잘 수용한다. 그러므로 웹소켓의 역할은 요청-응답 방식의 HTTP 프로토콜로는 쉽게 해결할 수 없었던 실시간 비동기 통신을 웹 브라우저에 제공함으로 웹 브라우저의 사용 범위를 넓혀주는 것으로 봐야 한다. 즉 실시간 비즈니스 시나리오가 포함된 멋진 웹 애플리케이션을 만들려면, HTTP 프로토콜과 웹소켓 프로토콜 중 어느 하나만을 고집하는 것이 아니라 서로의 장점을 함께 활용해야 한다. 편식은 언제나 몸을 해롭게 한다.

웹소켓은 표준이 2011년에 완성됐고 표준을 지원하는 브라우저도 2011년 이후 버전들이고 특히 대한민국에서 많이 사용하는 인터넷 익스플로러는 버전 10부터 지원되므로 아직은 웹소켓을 사용한 사례가 많지 않은 것도 사실이다. 그러나 웹소켓을 범용 서비스가 아닌 기업 통합 등 기업의 특화된 서비스에 이용한다면 실시간 정보의 사용에 있어서 전용 클라이언트 애플리케이션 못지 않게 훌륭한 웹 브라우저 애플리케이션을 개발할 수 있을 것이라 기대한다. 이 글을 통해 독자들도 웹소켓의 가능성에 주목할 수 있는 계기가 됐으면 한다.



참고 사이트


2014년 7월 2일 수요일

분산 비즈니스 트랜잭션과 오류 해결 방법


애플리케이션에서 우리가 일반적으로 트랜잭션이라 부르는 용어는 실제로 두 가지로 구분된다. 첫째, 시스템 트랜잭션(system transaction)[EAA]이다. 시스템 트랜잭션은 일반적으로 단일 데이터베이스 수준에서 처리되는 단일 트랜잭션을 말한다. 둘째, 비즈니스 트랜잭션(business transaction)[EAA]이 있다. 비즈니스 트랜잭션이란 하나 이상의 요청을 처리하기 위해 여러 시스템 트랜잭션을 포함한 트랜잭션을 말한다. 예를 들어 여행 예약을 진행하는 경우 항공 예약, 호텔 예약, 렌터카 예약, 기차 예약 등 여행하면서 타거나 머물거나 묵게 될 각 장소가 모두 예약돼야 완성되는 트랜잭션이 비즈니스 트랜잭션이다. 비즈니스 트랜잭션은 하나 이상의 요청과 일련의 시스템 트랜잭션을 포함하므로 완성되기까지 긴 시간이 필요하다.

일반적으로 기업은 위 두 트랜잭션과 더불어 분산 시스템 트랜잭션(distributed system transaction)과 분산 비즈니스 트랜잭션(distributed business transaction)이 필요한 경우가 있다. 우선, 분산 시스템 트랜잭션은 분산된 자원들이 트랜잭션 프로토콜에 따라 마치 단일 시스템 트랜잭션처럼 보이도록 하는 기술로 2단계 커밋(two-phase commit) 기술로 구현된다. 비즈니스 로직은 분산 트랜잭션을 시작한 애플리케이션에 집중된다. 반면, 분산 비즈니스 트랜잭션은 하나 이상의 요청에 분산된 비즈니스 애플리케이션들이 여럿 참여하고 참여한 애플리케이션이 일련의 시스템 트랜잭션들을 포함할 수 있는 트랜잭션을 말한다. 분산 비즈니스 트랜잭션은 시스템 트랜잭션이 하나 이상 여러 시스템에서 발생한다. 그러므로 분산 비즈니스 트랜잭션은 나머지 다른 모든 트랜잭션을 부분 집합으로 가질 수 있는 가장 일반적인 형태의 트랜잭션이다. 분산 비즈니스 트랜잭션은 비즈니스가 복잡한 만큼 다양한 형태로 등장할 수 있으므로 정형화된 아키텍처 패턴을 갖지 않는다. 분산 비즈니스 트랜잭션은 애플리케이션 수준에서 ACID를 보장하는 방안을 제공해야 한다.

트랜잭션들의 특성을 보면 다음과 같다.

구분
시스템 트랜잭션
비즈니스 트랜잭션
분산 시스템 트랜잭션
분산 비즈니스 트랜잭션
처리 시간
단시간(수초 이내) 장시간 단시간(수초 이내) 장시간
참여자
단일 로컬 시스템 단일 로컬 시스템 복수 분산 시스템 분산 복수 시스템
원자성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
영속성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
고립성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
무결성
알고리즘 보장 애플리케이션 보장 프로토콜 보장 애플리케이션 보장
장애 조치
리두 로그,
롤백 세그먼트 등
오류 해결 전략 수작업 복구 오류 해결 전략
장애 지점
적음
(CPU, 메모리, 디스크)
적음
(CPU, 메모리, 디스크)
많음
(자원, 네트워크, 관리)
많음
(자원, 네트워크, 관리)

일반적으로 분산 환경은 로컬 환경에 비해 여러 가지로 불리하다. (시스템 커밋이 발생하는 위치를 기준으로 분산 시스템 트랜잭션과 분산 비즈니스 트랜잭션을 분산 트랜잭션이라 하고, 시스템 트랜잭션과 비즈니스 트랜잭션을 로컬 트랜잭션이라 하자.) 분산 트랜잭션은 참여 시스템들이 분산됨으로, 경우에 따라 처리 시간이 길어짐으로, 네트워크가 개입함으로, 시스템 관리 주체가 달라짐으로 약점들을 갖게 된다. 만약 분산 트랜잭션에 참여하는 시스템들이 고성능이고, 네트워크도 시스템 버스만큼 성능과 안정성이 보장되고, 업무 흐름이 처리 중에는 절대로 다운이나 정비로 참여 시스템들을 멈추게 하지 않는다면 분산 트랜잭션은 로컬 트랜잭션과 동일한 트랜잭션이 될 것이다. 그러나 현실의 분산 환경은 절대로 로컬 환경과 같을 수 없다. 그러므로 분산 트랜잭션과 로컬 트랜잭션을 동일하게 가정을 하는 것은 현실에 맞지 않는다.

로컬 트랜잭션과 분산 트랜잭션은 로컬 프로시저 호출(Local Procedure Call)과 원격 프로시저 호출(Remote Procedure Call)[EIP]과 많이 닮아 있다. 로컬 프로시저 호출과 원격 프로시저 호출도 단일 프로그램, 단일 시스템, 단일 메모리 공간에서 클라이언트와 서버 프로그램, 네트워크로 연결된 두 시스템으로 실질적으로 다른 인프라 구조를 갖는다. 마찬가지로 로컬 트랜잭션과 분산 트랜잭션도 실질적으로 다른 인프라 구조를 갖는다. 그러므로 로컬 프로시저 호출에서 원격 프로시저 호출로 넘어 갈 때 고려해야 할 수많은 문제들이 로컬 트랜잭션에서 분산 트랜잭션으로 넘어 갈 때도 그대로 등장한다.

기업에서 네트워크를 통해 분산된 애플리케이션이나 시스템(자원)을 결합해 하나의 비즈니스 흐름을 완성하는 애플리케이션 통합은 비일비재하다. 그런데 이 경우 비즈니스 흐름에 참여하는 각 요소들이 원격지에 존재한다는 사실이 쉽게 간과된다. 그리고 로컬 프로그래밍에 익숙한 개발자들은 원격지의 서비스나 자원도 가능하면 로컬 메소드나 자원을 활용하는 것처럼 사용하고 싶어하는 경향이 강하다. 이런 요구로 등장하는 것들이 동기 방식의 원격 프로시저 호출이나 2단계 커밋 같은 기술들이다. 이들은 개발자에게 원격지 서비스나 자원을 마치 같은 시스템의 자원처럼 활용하게 해준다. 이 기술은 언뜻 보기에는 사용하기 편리한 기술처럼 보이지만 비즈니스의 안정성을 위해 상당한 기술적 복잡성과 성능을 희생하게 된다. 즉 참여 시스템(자원)들은 서로 단단히 결합(tight coupling)된다. 그러나 경우에 따라 제품의 도입을 주도하는 이해관계로 인해 이런 사실은 잘 알려지지도 알리려고도 하지 않는다.

분산 시스템의 주요 취약점들에 대해 좀더 살펴보자. 분산 시스템들의 통신은 로컬 시스템 버스 통신이 아닌 이더넷과 같은 저수준 프로토콜을 이용한 원격 통신이다. 그런데 네트워크는 본질적으로 불안정하다. 물론 최근 네트워크 장비들은 일년에 몇 시간도 안 되는 다운 타임을 보장하기도 한다. 그러나 장애 확률은 얼마나 많은 시스템들이 업무 흐름에 참여했느냐에 따라 개별 시스템들의 장애 확률의 합으로 나타난다. 즉 참여 시스템이나 장비들이 많을수록 확률은 높아진다. 예를 들어 업무 흐름에 포함된 10개의 시스템이나 장비가 2시간/년의 장애 시간을 보장하더라도 전체적으로 보면 20시간/년의 장애 시간을 갖는 것이다. 시스템은 게다가 각 시스템의 관리 담당자도 별도로 운영될 가능성이 높다. 이 경우 각 시스템의 담당자는 필요에 따라 시스템을 중지시키거나 다른 작업을 실행시킴으로 비즈니스를 보장하기 위한 가용성이나 성능을 보장받지 못하게 될 수도 있다. 더 중요한 점은 장애 발생 시 장애를 찾거나 복구하는데 점검하고 대처해야 할 지점의 증가로 장애 회복 시간이 가늠 없이 길어 질 수 있다는 점이다. 이런 문제에 대해 시스템을 오래 운영해 본 운영자들은 대부분 동의할 수 있을 것이다. 그러므로 분산 시스템은 분명이 단일 시스템과 다르게 접근해야 한다.

분산 환경이 갖는 취약성으로 인해 분산 시스템 트랜잭션은 해결하기 곤란한 문제를 야기할 수 있다. 즉 처리 시간 동안 참여 시스템이나 자원 중 하나라도 문제가 발생하면 거래는 실패한다. 그러므로 분산 시스템 트랜잭션 기술인 2단계 커밋도 완전한 기술이 아니다.[DTT] 2단계 커밋 기술은 단일 자원만 사용하는 단일 시스템 트랜잭션과 본질적으로 다르다. 2단계 커밋이 안전하다고 생각하는 것은 잘못된 생각이다.

일반적으로 2단계 커밋은 다음과 같은 문제들을 갖는다.

    1) 투표와 커밋 단계 사이 장애로 2단계 커밋에 참여한 전체 시스템의 무결성 훼손이 발생할 수 있으며, 이 경우 무결성을 복원하기 위한 복잡한 수작업이 필요하다.

    2) 2단계 커밋을 지원하지 않는 기존 시스템들과 협업하기 어렵다.

    3) 하나 이상의 요청과 장시간이 걸리는 애플리케이션 통합에 적합하지 않다.

첫째, 통신 장애는 분산 기술에 내재된 속성이다. 네트워크는 신호는 본질적으로 불안정하다. 2단계 커밋의 조정자가 아무리 잘 트랜잭션을 관리하더라도 투표와 커밋 단계 사이에서 네트워크나 참여 자원의 장애가 발생하는 경우 무결성은 파괴된다. 2단계 커밋에서 장애가 발생하면 심각한 문제가 뒤따른다. 일단 2단계 커밋의 오류가 발생하면 애플리케이션은 참여 단계를 프로그램적으로 특정하기 어렵다. 그러므로 2단계 커밋에 참여한 모든 시스템의 상태를 확인해야 하고 일부 시스템의 완료된 커밋과 일부 시스템의 미완료된 커밋을 어떻게 할 것인가에 대해 비즈니스 담당자와 데이터베이스 관리자들과 애플리케이션 담당자들이 모두 합의하는 결론을 도출해야 한다. 참여 자원의 완료된 커밋 조합에 따라 대응 결론이 달라질 수도 있다. 그러므로 2단계 커밋을 마치 단일 시스템 트랜잭션처럼 간단히 생각하고 애플리케이션에 포함시키는 것은 해결할 수 없는 문제를 안고 있는 애플리케이션을 운영하는 것이다. 2단계 커밋을 사용하더라도, 분산 시스템들의 무결성은 정교한 비즈니스 시나리오를 통해 애플리케이션이 해결해야 한다. 둘째, 기업 내 2단계 커밋을 지원하는 시스템이 실제로 많지 않을 수 있다. 그러므로 2단계 커밋을 지원하지 않는 애플리케이션과 협업할 때는 2단계 커밋을 결국 사용하지 못하게 된다. 그러므로 이 경우도 역시 분산 무결성을 정교한 비즈니스 시나리오 분석을 통해 애플리케이션이 해결해야 한다. 셋째, 일반적으로 비즈니스 프로세스는 일련의 시스템 트랜잭션을 포함하는 장시간 업무 흐름을 통해 완성된다. 장시간 처리 비즈니스 프로세스도 2단계 커밋을 적용하면 자원의 무결성과 고립성 문제는 어느 정도 해결할 수 있으나 참여 애플리케이션들 간 성능이나 부하 차이로 인해 전체 성능에 지나친 병목을 발생시킬 수 있다. 이런 경우 2단계 커밋이 제공하는 장점보다 성능 병목으로 인한 단점이 더 커지게 된다. 그러므로 애플리케이션들이 협업하는 애플리케이션 통합에는 2단계 커밋을 적합하지 않을 수 있다.

그러므로 2단계 커밋을 사용해도 좋은 분야가 있고 그렇지 않은 분야가 있다는 것을 반드시 명심해야 한다. 사용의 판단 기준은 애플리케이션의 특성을 먼저 고려한 후 사용 여부를 판단해야 한다. 예를 들어 참여 시스템의 수, 참여 시스템의 성능, 참여 시스템의 2단계 커밋 지원 여부, 비즈니스 프로세스 완성에 필요한 시간, 참여 시스템의 안정성 등등을 고려해야 한다. 일반적으로 2단계 커밋은 비즈니스 애플리케이션 로직이 최소화된 성능과 안정성이 보장된 데이터베이스 시스템들을 하나의 트랜잭션으로 엮는 경우 잘 동작한다.

결국 2단계 커밋을 사용하더라도 비즈니스 트랜잭션의 단계 별 상태를 정교하게 분석해야 하고 일부 성공, 일부 실패에 대한 대응책도 준비해야 한다. 그런데 우리가 2단계 커밋을 사용하는 이유는 이런 무결성 문제를 회피하기 위해서인데 결국 일련의 시스템 트랜잭션들에 대한 성공, 실패 대응책을 고려해야 한다면 2단계 커밋을 사용할 이유가 있겠는가? 혹은 필자가 자주 발생하지도 않는 2단계 커밋의 오류를 너무 확대 해석한 것일 수도 있고, 오류가 발생한다면 무시해 버리면 될 것이 아닌가 반문할 수도 있을 것이다. 그러나 만약에 2단계 커밋이 단순히 극히 짧은 순간의 무결성 보장을 위해 사용된다면 굳이 2단계 커밋을 사용할 이유는 또 무엇인가? 그리고 무결성이 훼손되더라도 무시해 버린다면 더더군다나 2단계 커밋을 사용할 이유는 무엇인가?

2단계 커밋을 사용하지 않더라도 비즈니스 트랜잭션의 무결성과 고립성은 낙관적 오프라인 잠금 패턴(Optimistic Offline Lock pattern)[EAA]이나 비관적 오프라인 잠금 패턴(Pessimistic Offline Lock pattern)[EAA], 성긴 잠금 패턴(Coarse-Grained Lock pattern)[EAA], 암시적 잠금 패턴(Implicit Lock pattern)[EAA] 등을 통해 별도로 보장할 수 있다.

애플리케이션을 통합해야 경우 충분히 고려되지 않은 2단계 커밋을 바로 적용하기 보다 비즈니스 시나리오를 좀더 신중하게 분석해야 한다. 또한 각 참여 시스템의 트랜잭션 단계마다 오류 대응책도 마련해야 한다. 그럼 트랜잭션 오류는 어떻게 해결해야 할까? 애플리케이션 개발자들이 ACID를 보장하기 위해 동기 방식이면서 미들웨어에 의존하는 시스템 트랜잭션이나 2단계 커밋의 관점이 아닌, 비동기 메시징과 애플리케이션 상태 관점에서 비즈니스 트랜잭션 오류에 대한 대응책을 생각해 보자. 비동기 메시징에서는 본질적으로 전체 비즈니스 흐름에 트랜잭션을 적용하기가 사실상 불가능하므로 발생한 오류는 비즈니스적으로 해결한다. 비즈니스 트랜잭션에 발생한 오류를 해결하는 전략들은 다음과 같다.

    1) 취소 전략: 비즈니스 트랜잭션의 오류 해결책으로 가장 간단한 전략이다. 즉 지금까지 진행된 개별 트랜잭션들을 모두 취소시킨다. 어떻게 보면 이 전략은 2단계 커밋의 롤백 전략과 유사하다. 그리고 별로 좋은 전략 같아 보이지도 않는다. 그러나 현실 세계에서는 그런대로 쓸만하다. 취소함으로 발생하는 손실이 거래를 복구함으로 얻는 이익보다 큰 경우 특히 유효하다. 애플리케이션 개발 시 실행(삽입, 갱신) 기능과 취소(삭제, 갱신)기능을 함께 고려하고, 트랜잭션 조정자는 오류를 감지한 후 각 참여 자원의 취소 기능을 요청한다. 2단계 커밋의 롤백과 다른 점은 하나 이상의 요청이 트랜잭션 중간에 개입될 수 있고 긴 시간의 트랜잭션에서 발생한 오류를 해결하는 전략으로, 이런 문제는 2단계 커밋의 롤백으로는 해결되지 않는다.

    2) 재시도 전략: 재시도를 통해 비즈니스 트랜잭션이 성공할 수 있는 업무인 경우 다시 동일한 비즈니스 기능을 시작한다. 예를 들어 외부 자원이 일시적으로 중지된 경우 재시도를 통해 극복될 수 있다. 업무 규칙을 위반한 비즈니스 기능의 요청인 경우 이 전략을 사용하면 안된다. 참여 시스템이 멱등 수신자(Idempotent Receiver)[EIP]가 된다면, 이 전략은 좀더 효과를 발휘한다. 멱등 수신자란 동일한 요구에 대해 수신자의 상태가 변하지 않는 수신자를 말한다. 즉 재시도 전략을 비즈니스 트랜잭션 오류의 전략으로 선택한 경우 각 참여 자원을 멱등 수신자로 만들어야 한다. 멱등 수신자 패턴에 대한 자세한 설명은 [EIP]나 [SDP]를 참조한다. 재시도에는 재시도 횟수를 지정할 수 있다. 재시도 횟수를 넘어선 오류인 경우 다른 해결 전략으로 오류 해결 방법을 변경해야 한다.

    3) 보상 전략: 이미 처리된 비즈니스 오류를 별도의 비즈니스 기능으로 보상하는 전략이다. 예를 들어 은행이 고객의 이체 처리 중 출금은 성공했으나 입금에 실패한 경우 출금 액만큼 은행이 고객에게 입금해주는 전략이다. 이를 위해서는 트랜잭션 오류 상태를 해결 상태로 전이할 수 있는 기능(메소드)을 참여 애플리케이션에 포함해야 한다. 비즈니스 트랜잭션 오류가 발생하면 비즈니스 트랜잭션 조정자는 보상 비즈니스 기능을 참여 시스템에 요청해 비즈니스 트랜잭션의 오류 상태를 정상 상태로 전이시킨다.

비즈니스 트랜잭션을 설계할 때 위 전략들 고려해서 오류 대응책을 마련한다면, 트랜잭션 조정자 애플리케이션이나 트랜잭션 참여 서비스나 자원 애플리케이션들의 역할도 분명해 질 것이고, 2단계 커밋의 미해결 상태의 위험성도 자동적으로 배제할 수 있을 것이다.

분산 애플리케이션 통합의 경우 비즈니스 트랜잭션의 성격을 정확히 분석해 취소, 재시도, 보상 전략 중 해당 트랜잭션에 적합한 전략을 결정해야 한다. 그리고 나서 비즈니스 트랜잭션 조정자, 참여 시스템들에 필요 기능을 구현해야 한다. 이 과정은 생각보다 간단한 과정은 아니다. 비즈니스 트랜잭션의 오류 해결에 대한 명확한 요구를 현업이 정립해야 하고 이에 따라 아키텍트나 개발자들은 트랜잭션 상태 관리, 오류 분석, 전략 실행, 오류 보고, 재시도 제어 등 필요한 요소들을 설계 및 구현해야 한다. 이 때 "기업 애플리케이션 아키텍처 패턴"[EAA]이나 "기업 통합 패턴"[EIP]이나 서비스 디자인 패턴[SDP] 등 기업 애플리케이션을 위한 패턴들을 적절히 활용하고, 필요한 경우 메시징 시스템이나 웹 서비스 시스템을 이용하고, 적절한 프레임워크를 이용할 수도 있을 것이다. 그러므로 이 접근은 시간이나 자원을 좀더 소비한다. 반면 분산 시스템 트랜잭션(2단계 커밋)의 무책임한 사용은 설계 구현을 간단하게 해 시간이나 자원을 절약하게 할지는 몰라도 서비스 중 발생하는 분산 비즈니스 트랜잭션의 오류를 완전히 해결하지 못한다.

비즈니스 트랜잭션 중 무결성을 훼손하는 일부 트랜잭션 오류가 발생 했을 때, 위 세 전략을 언제 적용할지를 생각해 보자. 아마도 애플리케이션 개발자는 가능하면 즉시 해결되도록 전략을 가동시키려 할 것이다. 그러나 시스템이나 네트워크 장애가 길어지는 경우 해결 요청은 어떻게 유지될 수 있을 것인가? 장애가 한 시간가량 지속됐고 무결성이 훼손된 분산 비즈니스 트랜잭션이 10건이라면? 이 오류들을 해결하기 위해 트랜잭션 조정자가 동기 원격 프로시저 호출을 지속적으로 호출해도 참여 시스템이나 네트워크가 복구되지 않는 한 해결되지 못할 것이다. 그러므로 이 문제를 해결하는 가장 좋은 방법은 비동기 방식을 사용하는 것이다. 즉 오류 해결 요청 메시지를 메시징 시스템 큐에 추가하는 것이다. 그러면 참여 시스템은 메시지 큐에서 요청 메시지를 수신해 필요한 해결 기능을 수행할 수 있을 것이다. 그런데 비즈니스 트랜잭션의 오류 해결 기능이나 정상적인 비즈니스 트랜잭션의 기능 수행이나 애플리케이션에서 본다면 별로 다르지 않은 기능이다. 그러므로 정상 비즈니스 트랜잭션은 동기 원격 프로시저 호출 방식으로 오류 해결 비즈니스는 비동기 메시징으로 개발하는 것은 같은 기능을 두 번 전혀 다른 패러다임을 사용해 구현하는 모양새가 된다. 그러므로 둘 중 한 가지 방법을 선택해야 한다. 필자는 비동기 메시징 방식의 애플리케이션 통합을 권장한다.

"기업 통합 패턴(Enterprise Integration Patterns)"의 저자 그레거 호프는 "스타벅스는 2단계 커밋을 사용하지 않는다."[Hohpe]란 제목의 글을 블로그에 게시한 적이 있다. 필자가 언급한 분산 비즈니스 트랜잭션 오류 해결 전략도 그의 글에서 인용한 것이다. 그러므로 기업 애플리케이션 통합에 분산 비즈니스 트랜잭션이 포함될 때, 2단계 커밋을 사용하려는 경우 2단계 커밋이 꼭 필요한지, 2단계 커밋을 사용할 수 있는지, 2단계 커밋의 오류 시 해결할 대응 전략이 있는지, 애플리케이션 수준에서 트랜잭션을 해결할 수 있는지, 트랜잭션 오류에 대한 해결 전략은 완비됐는지 등을 충분히 고려해야 할 것이다.


참고 자료