I/O 바운드와 CPU 바운드
"동시성(concurrency)"은 현대 프로그래밍에서 거의 빠지지 않는 주제이다. 수많은 기술서와 강연, 블로그가 동시성을 이야기한다. 동시성은 성능 최적화의 핵심 기술로 소개되며, 이를 제대로 이해하지 못하면 효율적인 시스템 설계가 불가능하다고 말하기도 한다. 역설적으로, 이렇게 많은 이야기가 오고 간다는 것은, 정작 동시성의 본질을 이해하고 제대로 활용하는 사람은 많지 않다는 걸 보여준다. 그 결과 동시성은 이해하기에는 너무 복잡한 개념처럼 보이며, 많은 개발자가 이를 오해하거나 과대평가하는 경향이 생길 수 있다.
동시성은 작업들이 동시에 실행되는 것처럼 보이게 만드는 기술이다. 하지만 실제로는 작업 자체가 동시에 실행되는 것이 아니라, 하나의 CPU가 여러 작업 스레드를 효율적으로 전환(context switching)하며 실행하는 방식을 가리킨다. 예를 들어, 네트워크 요청이 완료되기를 기다리는 동안 다른 작업을 처리하는 것이 동시성의 대표적인 사례이다. 이러한 과정으로 작업 처리량을 증가시킬 수 있기 때문에 동시성은 중요한 개념으로 여겨진다.
하지만 동시성은 직관적으로 이해하기 어려운 개념이다. 작업이 "동시에 실행되는 것처럼 보인다"는 설명은 실제로 어떤 일이 일어나는지를 정확히 표현하지 못한다. 더구나, 동시성과 병렬성을 혼동하거나, 동시성을 만능 도구로 여기는 잘못된 사례들이 동시성을 더욱 혼란스럽게 만든다. 이로 인해 동시성은 꼭 필요하지 않은 상황에서도 적용되거나, 적용해도 기대한 만큼의 성능 개선을 얻지 못하는 경우가 많다.
이러한 상황에서 동시성을 쉽게 이해하려면, 작업의 대기 시간에 주목하는 것이 매우 중요하다. 특히 I/O 작업은 동시성의 핵심 원리를 이해하는 데 좋은 단서를 제공한다. 네트워크 요청, 파일 읽기/쓰기, 데이터베이스 조회와 같은 작업은 대기 시간이 길고, 이 시간 동안 CPU는 실제로 아무런 작업도 하지 않는다. 동시성은 이러한 대기 시간을 다른 작업에 재활용함으로써 시스템의 효율성을 높인다.
I/O란 입력과 출력을 의미하며, 네트워크 요청, 파일 시스템 접근, 데이터베이스 조회 등이 이에 해당한다. I/O 작업은 긴 물리적 대기 시간이 필연적으로 발생하며, 이로 인해 CPU가 작업을 중단하고 기다려야 하는 상황이 생긴다. 이렇게 I/O 작업에 중점을 둔 작업을 흔히 "I/O 바운드"라고 불리며, 처리 성능이 주로 대기 시간에 의해 제한된다.
I/O 바운드 작업은 대기 시간이 많아 CPU가 놀게 되는 반면, CPU 바운드 작업은 연산이 주를 이루며 대기 시간이 거의 없다. 때문에 동시성은 주로 대기 시간이 많은 I/O 바운드 작업에서 빛을 발한다. 반면, 대기 시간이 적고 계산 시간이 많은 CPU 바운드 작업에서는 동시성이 큰 의미를 갖지 못하며, 오히려 병렬성(멀티코어 활용)이 중요해진다. 따라서 작업의 성격에 따라 동시성과 병렬성을 구분하고 적절히 적용하는 것이 매우 중요하다.
결론적으로, 동시성은 주로 I/O 바운드 작업을 효율적으로 처리하기 위한 기술이라고 볼 수 있다. 네트워크 요청과 같은 작업에서 대기 시간을 최소화하고, 이 시간을 다른 작업에 재활용하는 것이 동시성의 주된 목표이다. 이를 통해 응답성이 개선되고, 시스템 전체의 자원 활용도가 높아진다. 그러나 CPU 바운드 작업에서는 동시성이 그다지 유의미하지 않다. 이 점에서 동시성은 모든 상황에서 중요하다고 말하기 어렵다.
동시성은 대기 시간을 효율적으로 활용하기 위한 중요한 도구이지만, 모든 상황에서 필요한 것은 아니다. 특히 CPU 바운드 작업에서는 병렬성이 더 중요한 역할을 한다. 따라서 동시성을 제대로 이해하려면, 작업의 성격과 대기 시간의 본질을 이해하는 것이 더 중요하다. 그리고 업무에 있어 동시성 자체에 집착하기보다, 작업의 성격을 먼저 파악해야 한다. 서비스의 I/O 작업 비중, 대기 시간, 그리고 사용자의 기대치를 분석함으로써 동시성의 적절한 활용 여부를 판단할 수 있다. 결국, 동시성은 서비스의 본질적인 문제를 해결하기 위한 도구일 뿐이다. 이를 명확히 이해하는 것이 효율적이고 안정적인 시스템 설계의 첫걸음이다.