디자이너가 개발자와 일 하다 보니 (6)
실무에서 개발자와 협업 시 개발에 대한 전문적인 지식을 가지고 있는 것이 아니기 때문에 개발자분들이 최대한 쉽게 설명해주지만, 많이 언급되는 부분에 대해서는 정리해두고, 추후 동일한 내용이 등장한다면 더 심도 있는 대화를 하려고 노력한다.
이 글은 개발자가 읽어보기에는 다소 기초적인 내용일 수 있지만, 디자이너의 입장에서는 충분히 도움될만한 내용이라고 생각한다.
동기와 비동기로 작성한 코드의 가장 큰 차이 중 하나는 *런타임(runtime)시에 발생하는 지연시간(latency)이다. 또한 동기와 비동기는 어떠한 프로세스를 처리하는데 절차적인 측면이 강하다.
*런타임(runtime): 컴퓨터 프로그램이 실행되고 있는 동안의 동작을 말한다.
동기 방식은 데이터의 요청과 결과가 한 자리에서 동시에 일어난다. 즉, 사용자가 데이터를 서버에게 요청한다면 그 서버가 데이터 요청에 따른 응답을 사용자에게 다시 리턴해주기 전까지 사용자는 다른 활동을 할 수 없으며 기다려야 한다.
예를 들어, 메시지를 보낸다고 하면 메시지를 보내고 응답이 오기까지 아무것도 할 수 없다. 왜냐하면 앞에 작업이 아직 끝나지 않았기 때문이다.
ATM 기계, 키오스크, 단순 단말기 같은 방식은 이런 동기적인 방식을 따르고 있다.
동기 방식 장점
코드 파악이 쉽고, 유지보수, *디버깅이 쉽다. 설계가 매우 간단하고 직관적이다.
*디버깅: 디버깅(debugging) 또는 디버그(debug)는 컴퓨터 프로그램 개발 단계 중에 발생하는 시스템의 논리적인 오류나 비정상적 연산(버그)을 찾아내고 그 원인을 밝히고 수정하는 작업 과정을 말한다.
동기 방식 단점
데이터 베이스 작업이 완료되어 데이터가 오기까지 기다린다면, 우리의 애플리케이션은 *유휴(Idle) 상태가 되고, 다른 작업을 할 수 있는 시간이 낭비하게 된다. 또한 웹에서 UI를 이런 식으로 구현하게 되면, 브라우저가 응답하지 않는다 라는 메시지를 계속해서 제공하는 최악의 UX를 제공할 수 있다.
*유휴(CPU): 컴퓨터 처리 장치에서 유휴(CPU) 또는 아이들(Idle)은 어떠한 프로그램에 의해서도 사용되지 않는 상태를 말한다.
반대로 비동기 방식은 요청을 보냈을 때 응답 상태와 상관없이 다른 외부 활동을 수행하여도 되고, 서버에게 다른 요청사항을 보내도 상관없다. 즉 A 작업이 시작하면 동시에 B작업이 실행된다. A 작업은 결과 값이 나오는 대로 출력된다.
비동기 방식 장점
요청에 따른 결과가 반환되는 시간 동안 다른 작업을 수행할 수 있다.
비동기 방식 단점
비동기 방식은 지속적으로 응답할 수 있게 해 주지만, 동기적 방식에 비해 많은 비용이 발생한다.
개발자 제이콥(JACOB), 동기(Synchronous) vs 비동기(Asynchronous) 그리고 IOC, 2020.02.01
자바스크립트의 관점이 아닌 네트워크 통신에서의 블로킹과 논블로킹을 아래의 영상을 참고하여 정리해보았다.
일반IT, Blocking / Non-Blocking Socket, 2018.04.02
*TCP로 통신하다 보면, 거래량이 적은 경우 문제없던 시스템이 거래량이 많아지면 시스템에 Block현상(멈춰있는 현상)이 일어난다. 대부분 *socket과 관련된 connect, accept, send, receive 등과 같은 함수를 호출하는 도중에 발생하는 현상이다.
*TCP(전송 제어 프로토콜, Transmission Control Protocol): 인터넷에 연결된 컴퓨터에서 실행되는 프로그램 간에 바이트를 에러 없이 교환할 수 있게 한다.
*socket: 네트워크 통신을 위한 프로그램들은 소켓을 생성하고, 이 소켓을 통해서 서로 데이터를 교환한다.
간략하게 함수 정리
listen() 함수: 소켓의 연결을 위한 대기열 생성
accept() 함수: 클라이언트 연결을 수락하는 함수
connect() 함수: 생성한 소켓을 통해 서버로 접속을 요청하는 함수
write() 함수: 파일에서 데이터를 출력하는 함수
read() 함수: 파일을 읽는 함수
socket() 함수: 시스템과 시스템 간의 입구이며, 이 함수를 통해 다양한 소켓을 만들 수 있다.
fcntl 함수: 파일 속성 변경, 컨트롤할 수 있는 함수
그림 1, 서버에서 소켓을 열고 bind 하고 listen(소켓의 연결을 위한 대기열 생성)한 다음, accept(클라이언트 연결을 수락하는 함수) 상태에서 기다리게 된다. Block상태에 빠지게 되는 첫 번째 함수이다.
대기하고 있으면, 클라이언트에서 소켓을 열고, connect(생성한 소켓을 통해 서버로 접속을 요청하는 함수)을 할 때 해당 서버 IP와 포트로, connect요청을 하게 되면, 내부적으로 *3-way handshake을 하게 된다. SYN을 보내고 SYN+ACK를 보내고 다음 ACK를 다시 받아야 서버 입장에서는 접속을 성립(establish)하게 된다. 하지만, 서버 입장에서 accept를 하고 기다리고 있는데, 만약 ACK가 오지 않게 되면, 서버 입장에서는 무한으로 기다리게(Blocking) 된다.
*3-way handshake: 서버와 클라이언트 간에 데이터 전송하기 전에 준비가 되었는지 알 수 있게 하는 과정이다.
그림 2, connect 되어 소켓이 붙었는데, 클라이언트가 일반적으로 write(파일에서 데이터를 출력하는 함수)를 하게 되면, 버퍼에 write이 붙게 되고, 서버 측으로 넘어간 *버퍼를 read(파일을 읽는 함수)를 통해서 읽게 된다. 하지만, 리드를 하려고 해도 버퍼에 데이터가 없는 상황이라면, 서버 입장에서는 계속 기다리면서 데이터가 들어올 때까지 기다려야 한다. 클라이언트가 소켓을 연결해놓고 write를 하지 않게 되거나 어떤 장애로 데이터가 넘어오지 않게 되면, 서버는 더 이상 일을 처리하지 못하고 계속 Block 상태가 되어서 결과적으로 서버가 멈추게 되는 현상이 발생할 수 있다.
*버퍼: 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역이다.
그림 3, 일반적으로 잘 해결되면, 클라이언트에서 write시 서버 입장에서 데이터를 받아서 read 하고, 서버 업무 처리 후, 다시 write 하게 되면, 클라이언트가 다시 read 하게 되는, 순환하면서 클라이언트와 서버 간에 통신이 이뤄지는 것이 가장 일반적인 현상이다.
그림 4, 서버가 프로세스를 하나 띄어 놓고 기다리고 있다. 클라이언트가 접속했다. 두 개 간의 통신은 아무 문제가 없다.
그림 5, 그러나 동시 접속하는 클라이언트 수가 많아지게 되면, 프로세스와 잘 연결되어 있는 클라이언트는 통신할지 몰라도, 나머지 클라이언트 들은 상대방 서버에 연결할 수 있는 소켓이 없기 때문에, 클라이언트 입장에서 Waiting 상태에 빠지게 된다. 만약 서버 측이라면, 이 문제가 발생했을 때 큰 민원을 받게 될 것이다. 그래서 이것을 해결하기 위한 여러 가지 방법 중 하나는 multi-tasking 방식이다.
그림 6, 예를 들어 클라이언트가 3개가 있으면, 클라이언트 수만큼 서버의 프로세스 개수를 늘려놓는 것이다. 이렇게 되면 통신이 원활하게 된다. 이것을 muli-processing라고 한다.
그림 7, 그런데 만약 더 많은 클라이언트가 필요하다면 결국 마지막 클라이언트는 똑같은 현상을 마주하게 될 것이다. 하지만 계속해서 프로세스 수를 늘리는 것도 문제가 된다. 프로세스를 많이 늘리게 되면, 서버 측에서 메모리의 부담을 느끼게 되고, 잦은 *컨텍스트 스위칭이 일어나게 된다.
*컨텍스트 스위칭(Context Switch): CPU안에서 프로세스가 돌아가는 것은 모든 프로세스가 한꺼번에 돌아가는 것이 아니라 서로 돌아가면서, a프로세스가 돌 때 b프로세스는 잠시 쉬고, b프로세스가 돌 때 a프로세스는 잠시 쉬고, 이렇게 서로 쉬면서 데이터를 PCB라는 곳에 잠시 저장했다가 CPU가 일할 때, 다시 넘겨주는 PCB와 CPU 간에 데이터 이동이 이뤄지게 된다. 그래서 프로세스가 많아지게 되면 CPU입장에서는 잦은 컨텍스트 스위칭이 일어나게 때문에 부담을 많이 느끼게 된다.
그림 8, multi-plexing은 하나의 프로세스에 여러 개의 소캣을 관리하는 방식을 말한다. 앞에 설명한 여러 개의 프로세스가 아니라 하나의 프로세스가 여러 개의 클라이언트를 관리하는 방식이다.
그림 1, 클라이언트 write에서 데이터가 오지 않으면 block 상태로 빠지게 된다.
반대로, Non-block 상태로 만들러면, fcntl이라는 함수를 통해 소켓을 Non-block 상태로 만들 수 있다. 서버에서 read를 할 때 만약 데이터가 없다면, 기존에는 데이터가 들어올 때까지 Blocking상태에 빠지지만, fcntl이라는 함수로 인해 Non-block상태가 되었기 때문에, 데이터의 유무에 상관없이 read 해보고 업무를 처리할 수 있게 되었다.
*EINTR : 데이터가 없다는 것을 의미한다. read 했는데 데이터가 없다면, 다시 올라가서 read 하면서 업무를 처리할 수 있다.
read 했는데 데이터가 있으면 업무 1일 처리하고. 데이터가 EINTR이면 그에 맞는 다른 업무를 처리하면서 계속 read 한다.
*fcntl 함수: 파일 속성 변경, 컨트롤할 수 있는 함수이다.