2012년 8월 20일 월요일

동기 호출(Synchronous Call)에 대한 고찰


필자가 올린 “동기 호출(Synchronous Call), 비동기 호출(Asynchorous Call)” 블로그에서는 동기 호출, 비동기 호출의 정의에 대해 설명했었다. 여기에 그 정의를 다시 옮겨 보면 다음과 같다.
애플리케이션 프로그램에서 동기 호출(Synchronous Call)이란 애플리케이션 프로세스(스레드)에서 하부작업(프로시저, 함수, 메서드) 요청 시 요청된 하부작업이 진행되는 동안 호출 프로세스(스레드)의 실행 흐름이 멈추게 되는 호출을 말한다.
애플리케이션 프로그램에서 비동기 호출(Asynchronous Call)이란 애플리케이션 프로세스(스레드)에서 하부작업 요청 시 요청된 하부작업의 실행 또는 종료와 관계없이 호출 프로세스(스레드)의 실행 흐름은 계속되는 호출을 말한다. 이런 호출 방식에서는 하부작업의 실행완료 시점을 호출 프로세스(스레드)가 정확이 알지 못하므로 호출 프로세스(스레드)와 하부작업은 하부작업의 실행 결과를 둘 사이 약속된 결과 영역 조회나 하부작업이 호출 프로세스(스레드)를 호출하는 Callback 메커니즘을 통해 확인한다.

지금까지 컴퓨터는 빠른 계산 능력을 사용하여 사용자에게 최대한 빠른 응답을 제공하는 방향으로 발전해 왔다. 그 결과 사용자는 컴퓨터에 어떤 요구를 하던지 거의 실시간으로 컴퓨터로부터 응답을 받게 되었다. 만약 컴퓨터에 요구한 응답이 몇 초 이상 걸리게 되는 경우 사용자는 몇 초의 기다림에 불안감까지 느낄 정도로 컴퓨터는 사용자의 요구에 대한 신속한 처리를 보장해 주었다.

이런 빠른 응답을 제공하기 위하여 컴퓨터 하드웨어는 무어의 법칙에 따라 18개월마다 성능을 두 배씩 성능을 향상해 왔다. 그러나 그 하드웨어에서 동작하는 프로그램은 상당히 정체된 발전 단계를 거쳐 왔다. 한 언어가 주류 언어가 되면 적어도 10년 이상 그 언어로 프로그램은 작성되고 그 뒤로도 몇 십 년을 사용하는 방식이었다. 예를 들어 현재 주류 언어 중 하나인 C (대표적 구조적 프로그램 언어) 언어는 태어난 지 이미 40년이 지난 언어이고 Java (객체 지향 언어) 언어도 1995년에 때어나 벌써 17년이나 지나고 있다. 이렇게 하드웨어 발전 속도와 프로그램 언어 사이에 발전의 차이는 프로그램에서 발전된 하드웨어를 최대한 활용하는 점점 어려운 상황에 접어 들게 되었다.

예를 들어 하드웨어 CPU가 늘어났지만 프로그램은 한 CPU에서 밖에 동작하지 못한다던 지, 하드웨어 레지스터 크기가 확장되어 더 많은 정보를 표현할 수 있지만 프로그램 언어의 자료형이 지원을 하지 못한다던 지 등 하드웨어에 도입된 중요한 기술을 프로그램은 좀더 오랜 기간을 거쳐야 비로소 그 기술에 적응하고 활용할 수 있게 된다. 그 중에서도 작업 처리 측면에서 보면 현재 주류 프로그램 언어들이 가진 순차적 명령 처리를 패러다임은 이런 하드웨어 성능 활용에 최대 걸림돌이 되고 있다. 즉 모든 실행은 순서를 가지고 하나씩 실행해 나가는 방식으로 어떤 일이던지 시작에서 끝까지 실행 흐름이 줄을 서서 실행된다. 이 방식은 객체 지향 언어에서도 마찬가지인데 여기서는 액터와 오브젝트 사이의 역할을 나누지만 실행 흐름에 대해서는 순차적 명령 처리 패러다임의 범주에서 벗어나지 못하고 있다. 즉 하부작업 요청 시 함수(메서드) 호출과 결과 리턴이라는 기본 골격은 구조적 프로그래밍 언어와 같다.

일반적으로 프로그램은 실행 흐름작성, 하부작업 실행, 하부작업 실행 결과 활용 등으로 작성된다. 이 과정에서 하부작업 실행과 그 결과를 구성하는 방식은 일반적으로 동기 호출 방식을 사용하게 된다. 즉 하부작업 요청 시 호출 쪽 본작업은 실행을 멈추고 하부작업이 리턴 될 때까지 대기하는 방식이다.

동기 호출은 하부작업의 결과물 획득에 순차적 처리가 가능한 리턴이라는 직관적이고 간결한 메커니즘을 제공한다. 즉 동기 호출은 하부작업이 진행되는 동안 호출 프로세스(스레드)는 실행이 중지되고 하부작업의 리턴 시 자동으로 실행이 계속되어 실행의 순차적 논리흐름을 잘 표현할 수 있다. 이에 비하여 비동기 호출은 하부작업의 완료까지 프로세스(스레드)가 대기하지 못하므로 실행의 순차적 논리흐름을 표현하는데 어려움이 있다. 좀더 구체적으로 비동기 호출은 하부작업의 결과물을 획득하는 메커니즘으로 Call Back 방식을 통한 결과 획득 또는 약속된 장소에 하부작업 결과를 확인하는 Poll 방식이 있는데 Call Back 방식은 호출 프로세스(스레드)의 작업에 대하여 순차적 처리 흐름을 보장하지 못하고 Poll 방식은 호출 프로세스(스레드)의 순차적 작업 흐름 처리는 가능하지만 동기 방식에 비하여 특별한 장점도 없으면서 처리 과정만 복잡한 문제를 일으킨다. 결론적으로 동기 호출은 실행의 순차 처리가 쉽고 비동기 호출은 실행의 순차 처리가 어렵다. 이런 측면에서 애플리케이션 개발 시 동기 호출은 비동기 호출에 비해 훨씬 지배적인 호출 방식으로 활용되고 이다.

그럼 이제부터 동기 호출 방식에 몇 가지 질문을 해보자 우선 처리 시간의 관점에서 동기 호출을 살펴보자. 일반적으로 동기 호출은 즉시 답을 요구하는 실시간 애플리케이션에 적합한 호출 방식이다. 즉 동기 호출의 처리는 아주 짧은 순간에 처리를 마쳐야 하는 경우에 주로 사용된다. 그런데 동기 호출을 미시적으로 살펴보면 (여기에서는 단일 애플리케이션에서 동기 호출을 살펴본다.) 호출 명령에서 호출이 일어난 실행 주소를 보관하고 하부작업 주소로 실행 주소를 옮겨 하부작업을 실행하고 하부작업 실행 결과를 저장하고 다시 호출 실행 주소로 작업을 옮겨 실행을 계속하게 된다. 이 과정에서 하부작업의 실행 시간이 반드시 일정 시간 필요하게 되는데 결과적으로 하부작업 요청시각(t0)와 하부작업 리턴 시각(t1) 사이 Δt = t1 – t0 이 진행된 후 본작업은 다음 작업을 할 수 있게 된다. 다시 말해 동기 호출도 엄밀히 말하면 동시성을 보장하지 못하고 반드시 비 동시성을 전제하는 호출인 것이다. 이렇게 동기 호출에 대하여 하부작업 처리를 위한 처리 시간이 필요하다는 것을 이해 한다면 결국 동기 호출도 실행 흐름은 순차적이지만 호출에 따른 실행 시간은 비동시적인 것이다.

동기 호출의 가장 큰 특징은 실행 흐름의 제어가 손쉽다는 것이다. 즉 앞에서 말한 하부작업 호출과 그 결과 획득의 프로그램의 간결성이다. 동기 호출을 사용하면 애플리케이션은 하부작업 처리 결과를 획득하기 위하여 별다른 노력 없이 단순히 함수(메서드)만 호출하면 된다.
int in, out;
in = 1;
out = subprocess(in);
System.out.println(in + " => " + out);
위 Java 소스 세 번째 줄을 보면 본처리 프로그램에서 하부처리 메서드 호출과 그 처리 결과 획득과정이 단 한 줄로 표현되는 것을 볼 수 있다. 이와 같은 동기 호출의 사용은 단순한 구조를 가지고 있고 단일 애플리케이션에서 분산 애플리케이션까지 일관되게 적용된다. 즉 분산 애플리케이션에서도 단일 애플리케이션의 호출 방식과 같은 방식으로 동기 호출을 사용한다. 그러나 이런 편리한 동기 호출을 지원하기 위하여 분산 애플리케이션은 통신 프로토콜, 하부 라이브러리, 프레임워크 부분에서는 상당히 복잡한 동기화 작업들을 구현하고 있다.

그럼 분산 애플리케이션에서 동기 호출 시 하부작업의 처리에 문제가 발생하면 어떻게 될까? 만약 하부작업에서 흐름제어를 관리할 수 있는 정도의 문제가 발생하는 경우에는 하부작업은 오류를 리턴하면 된다. 그러나 만약 하부작업의 문제가 흐름제어를 관리할 수 없을 정도의 심각한 문제가 발생하여 하부작업이 리턴을 할 수 없게 되면 전체 실행 흐름은 정지하고 애플리케이션은 정지(Hang-up)상태가 된다. 그리고 요구된 거래는 성공도 실패도 알 수 없는 상태에 이르게 될 것이다. 이렇게 하부작업에 흐름제어를 잃는 장애가 발생하면 애플리케이션은 중대한 문제 상황에 빠지게 된다. 그렇지만 일반적인 동기 호출에서는 하부작업 호출 시 타임아웃을 설정할 수 없다. 만약 하부작업 호출 시 타임아웃을 설정하고 실행 흐름 제어를 회복하기 위해서는 호출 프로세스(스레드)에 하부작업 완료를 확인하는 별도 스레드 등을 추가해서 하부작업을 모니터링 하는 기능을 추가 해야 한다. 그러나 일반적인 동기호출 사용 패턴에서 이런 문제까지는 고려는 되지 않고 있다. 예를 들어 J2EE 동기 호출 규격에도 흐름제어 회복을 위한 타임아웃에 대한 고려는 없다. 그러나 애플리케이션 운영측면에서 동기 호출 방식의 하부작업 호출에서 흐름제어 회복을 위한 타임아웃을 고려하지 않는다면 정지된 본작업 프로세스(스레드)는 아무런 일을 하지 않는 좀비가 되고 시스템 자원만 점유하게 되어 시스템 자원 가용성 및 성능에 영향을 줄 수 있다. 그리고 하부작업 흐름제어 회복 불능 장애 발생 시 더 심각한 애플리케이션 비즈니스 처리 상태의 불확실성을 만들어 낼 것이다.

동기 호출 패턴이 타임아웃 처리에 대한 관심이 적은 이유는 과거 단일 시스템에서 단일 프로세스로 동작하는 애플리케이션에서 사용하던 함수 호출에 대한 경험 때문일 것이다. 단일 프로세스 환경에서는 프로세스 내 실행 흐름이 하나 밖에 없기 때문에 본작업이던 하부작업이던 모두 같은 실행 흐름 내에 존재한다. 그러므로 하부작업이 흐름제어를 회복할 수 없는 문제가 발생하면 이미 전체 애플리케이션의 문제이기 때문에 애플리케이션은 동일한 정지(Hang-up) 현상을 보이게 된다. 그러나 분산 애플리케이션에서는 본작업의 실행흐름을 가진 프로세스(스레드)와 하부작업의 흐름을 가진 프로세스(스레드)가 각각 독립적인 실행 흐름을 가질 수 있다. 그 결과 동기 호출 시 본작업에서 요청한 하부작업의 실행흐름이 불능인 경우에도 본작업 프로세스(스레드)의 실행 흐름은 영향을 받지 않고 다음 처리를 할 수 있다. 단 본작업이 하부작업의 실행흐름을 잃는 장애에 대하여 자신의 실행 흐름을 계속하기 위해서는 본작업은 하부작업의 처리시간을 한정하는 타임아웃 기능을 추가해야 한다. 그러나 앞서 말했듯이 동기 호출에 대한 타임아웃 고려는 아직 개별적 고려 사항으로 일부 라이브러리나 규격에서 설정 또는 패러미터 입력으로 등장하고 있는 정도이다.

분산 애플리케이션 환경에서 동기 호출의 안정성에 대한 문제를 살펴보자. 일반적으로 단일 하드웨어에서 단일 애플리케이션의 동기 호출은 충분히 신뢰할 수 있다고 볼 수 있다. 그러나 분산 애플리케이션 환경에서 동기 호출은 시스템 하드웨어, 네트워크 등 관련된 구성 요소들이 모두 충분히 신뢰할 수 있다는 전제하에 안정성을 기대할 수 있다. 그러나 분산 애플리케이션에서는 아래와 같은 경우 동기 호출에 대한 신뢰성 문제를 가질 수 있다.

  1. 복수 시스템에 각각 동작하는 애플리케이션들 사이 동기 호출이 발생할 경우 시스템과 시스템의 네트워크 안정성은 프로세스 내 단일 애플리케이션의 하부작업 호출에 사용되는 시스템 내부 버스의 안정성보다 상당히 낮다. 그리고 네트워크 장애 발생 시 동기 호출로 연동되는 일련의 애플리케이션은 모두 중단될 수 밖에 없다. 
  2. 복수 시스템에서 각 시스템의 애플리케이션이 동기 호출을 사용하는 경우 상대 시스템의 안정성을 기대할 수 없는 경우 가 있다. 다시 말해 각 시스템에 소유 주체가 다름으로 각 시스템에 동작하는 애플리케이션의 동작 여부를 항상 보장 받지 못할 수 있다. 어느 쪽이던 시스템의 장애 또는 애플리케이션에 장애가 발생하면 동기 호출로 연동되는 일련의 애플리케이션은 모두 중단될 수 밖에 없다.

동기 호출 메커니즘은 프로그램 언어가 등장한 이래 가장 일반적인 작업 메커니즘이었다. 그러나 하드웨어가 발전하고 시스템들은 네트워크를 통해 연결되고 단일 애플리케이션에서 분산 애플리케이션으로 작업 규모가 커지면서 동기 호출의 한계가 점점 등장하고 있다. 동시성 또는 아주 짧은 처리 시간을 요구하는 업무들을 처리하는 방식에 동기 호출은 적합하지만 동시성을 지원하기 위하여 시스템 안정성, 네트워크 안정성, 애플리케이션 안정성을 모두 보장해야 하는 어려움이 따르게 되었다. 이런 어려움 속에서도 동기 호출 방식의 패러다임은 여전히 가장 선호하는 호출 방식으로 세상을 지배하고 있다. 그러나 분산 애플리케이션 환경에서 동기 호출을 사용할 때 하부작업 처리 시간, 실행 제어 회복, 분산 구간의 안정성 등을 고려해야 하므로 동기 호출 패턴이 예전 단일 애플리케이션과 비교하여 그렇게 단순하지 않게 되었으며 이와 관련하여 대안적인 패턴으로 비동기 호출의 필요성이 재 발견되고 있다.

지금까지 기술적 측면에서 동기 호출에 대한 몇 가지 사항을 생각해 보았다. 그런데 우리의 일상적인 비즈니스 패턴은 어떤 패턴일까도 고민해 볼 필요가 있다. 우리들이 살아가는 세상은 동기 호출 방식이 지배하는 세상일까 비동기 호출 방식이 지배하는 세상일까? 우리는 개인, 조직, 국가, 세계와 어떻게 의사 소통하며 살아가는가에 대하여 좀더 고민해 볼 필요가 있다. 그래야 동기, 비동기 문제를 좀더 객관화시킬 수 있고, 우리 생활과 잘 접목되는 애플리케이션 사용 방향을 찾을 수 있을 것이다.

댓글 없음:

댓글 쓰기