애플리케이션의 비동기 스레드가 느릴때 OPENMARU APM을 이용한 원인분석 방법_chapter 1
오픈마루 APM에서는 JAVA의 비동기 스레드를 어떻게 모니터링할 수 있을까요?
동시에 여러 가지 작업을 처리하기 위해서는 어떤 방식을 사용할 수 있는지 알아보고, Java에서는 비동기 스레드를 어떻게 프로그래밍 하는지 살펴보려고 합니다.
Java 기반 미들웨어의 성능 및 트러블 슈팅을 위해서는 전체 서비스 중에서 어떤 구간이 느린지 정확히 식별하고 그 부분을 튜닝해야 더욱 빠르고 안전한 서비스를 만들어 낼 수 있을 것입니다.
프로세스와 스레드
엔지니어 및 개발자 채용 면접에서 자주 묻는 질문 중의 하나가 “프로세스(Process)와 스레드(Thread)의 차이점에 관해서 설명해주세요”일 것입니다.
그만큼 중요한 개념이라는 것입니다. “프로세스”는 컴퓨터에서 실행되고 있는 컴퓨터 프로그램을 말하고, “스레드”는 프로그램 즉, 프로세스 내에서 실행되는 흐름의 단위를 의미합니다.
간단히, 컴퓨터의 하드디스크에 있는 프로그램을 실행한 것을 프로세스라고 할 수 있습니다. 동작 중인 프로세스 내에서 여러 작업을 동시에 수행하기 위해서 컴퓨터의 가장 비싼 자원 중의 하나인 CPU를 나눠 사용해야 합니다. 이때, 스레드를 이용하여 시간별로 CPU 자원을 나눠 사용할 수 있습니다.
물론 프로세스를 여러 개 동시에 띄워서 사용해도(Multi-Process) 동시 처리가 가능합니다만, 각각의 프로세스별로 별도의 독립적인 주소공간(코드, 스택, Heap)을 갖기 때문에 프로세스 간에 정보를 공유하기 위해서는 IPC(Inter-Process Communication)처럼 복잡한 방식으로 서로 데이터를 주고받아야 합니다.
반면 프로세스 내에서 여러 개의 스레드를 사용하는 멀티 스레드(Multi Thread) 방식은 Stack은 스레드 별로 별도로 관리하지만, 나머지 코드와 Heap은 스레드 간에 공유가 가능합니다. 동시 작업을 처리할 때 더 간단하고 빠른 처리가 가능합니다.
그럼, 컴퓨터에서 무엇을, 왜, 동시에 처리해야 할까요?
지금은 멀티 CPU, 멀티 Core가 일반화되어 더욱더 동시 작업이 필요하지만, CPU가 1개만 있는 상황에서도 컴퓨터를 이용하여 여러 작업을 동시에 실행함으로써 컴퓨터에서 가장 비싼 자원인 CPU를 최대한 사용하기 위해서입니다. 멀티스레드를 사용하지 않을 경우 PC의 탐색기에서 파일을 검색하면, 검색이 끝날 때까지 아무 작업을 하지 못한다는 것입니다.
멀티 프로세스와 멀티 스레드가 정말로 꼭 필요한 경우는 동시에 많은 사용자의 요청을 처리해야 하는 서버 측 애플리케이션입니다.
실제로 Apache 서버는 최초에는 프로세스를 fork 하여 자식 프로세스를 만들어 각각의 프로세스가 1명의 웹 브라우저 요청을 처리했습니다. 이후 메모리 등의 자원은 더 적게 사용하면서도, 더 빠르게 처리하기 위하여 Worker MPM(Multi-Processing Module)로 스레드 방식을 사용할 수 있습니다. 프로세스와 스레드를 조합하여 동시에 처리할 수 있는 처리량(Capacity)을 늘릴 수 있습니다. (참고: http://www.opennaru.com/jboss/apache-prefork-vs-worker/)
스레드를 사용하면 프로세스 내의 자원들과 메모리를 공유하기 때문에 메모리 사용량과 시스템 자원 사용량이 줄어듭니다. 스레드 간에 데이터를 주고받을 때도 복잡하게 통신할 필요 없이, 직접 사용할 수 있기 때문에 더욱 간단하고 빠릅니다.
스레드를 사용한다고 다 좋은 것은 아닙니다. 하나의 프로그램에서 동시에 처리해야 하는 만큼 코딩이 복잡해 집니다. 특히 자원을 공유할 수 있기 때문에 여러 개의 스레드가 동시에 한 데이터를 업데이트하면 다른 스레드가 엉뚱한 값을 읽어올 수도 있습니다. 이런 문제가 발생하면 재현하여 찾기도 어렵습니다. 이걸 방지하기 위해서는 자원에 대한 동기화, 내가 사용하는 동안에 다른 스레드가 건드리지 못하도록 Lock을 거는 것이 꼭 필요합니다. 또, 스레드를 많이 사용하면 오히려 더 느려질 수 있습니다. 컨텍스트 스위칭(Context Switching), 문맥 전환 때문인데요. CPU에 연산을 하기 위해서는 Register에 데이터가 세팅되어 있어야 합니다. 동시에 여러 작업(실제로는 시분할, 순차작업이겠죠)을 하려면 현재 실행 중인 Register값을 어딘가 보관해 놓고, 다른 작업을 위한 값을 Register에 다시 읽어들여 작업하고, 다시 또 다른 작업 Register를 올려 작업하는 것이 반복될 것입니다. 이런 작업이 너무 많다면, 실제 작업에 소요되는 시간보다 작업을 전환하기 위해 필요한 작업에 오히려 더 많은 시간을 사용하여 시스템이 극도로 느려질 수 있는데, 이것을 컨텍스트 스위칭이라고 합니다. 사람도 마찬가지로 너무 많은 일을 동시에 하고 있으면, 무슨 일하나 제대로 하지 못하는 상황이 될 수 있겠죠.
우리가 WAS(Web Application Server)라고 말하는 Java 기반 미들웨어들은 동시에 많은 사용자의 요청을 처리하기 위하여 멀티스레드를 사용하고 있습니다.
Sync vs. Async, Blocking vs. Non-Blocking?
동기(Synchronous)와 비동기(Asynchronous)와 또 Blocking, Non-Blocking이란 이야기를 들어보셨을텐데 이 차이점을 설명할 수 있을까요?
동기, 비동기란 일을 누가 처리하는 지에 대한 것입니다. 일을 나 혼자 처리한다면, 동기(Sync)처리. 일을 다른 사람에게 시켜서 처리한다면 비동기(Async)입니다.
또 어떤 일이 끝나기를 기다리느냐(Blocking) 기다리지 않고 다른 일을 하느냐(Non-Blocking)에 따라 Blocking, Non-Blocking을 구분할 수 있습니다.
특히 CPU 입장에서는 I/O처리가 끝나기를 기다리게 되면 CPU는 아무 일을 못하고 기다리게 되니 효율이 좋진 않을 것입니다. I/O API를 호출하고 다른 일을 하다가 끝나면 그 결과를 받게 되면 CPU는 그동안 다른 일을 하고 있을 테니 훨씬 더 많은 일을 할 수 있게 됩니다. 컴퓨터 입장에서는 디스크의 파일 읽기, 네트워크 요청 읽기 등이 시간이 오래걸리는 매우 느린 작업일 것입니다. 이런 작업을 Non-Blocking으로 처리한다면 CPU를 더 많이 활용할 수 있는 방법이 될 것입니다. I/O에 대한 처리가 Blocking, Non-Blocking에서 효율을 높이는 방법입니다.
정리하면 아래와 같습니다. 비동기, Non-Blocking이 가장 효율이 좋은 방법이 될 것입니다. 물론 Java에서도 Non-Blocking I/O(nio)를 지원하지만, 일반적으로 비동기 스레드를 많이 사용하여 프로그래밍 할 것입니다.
이번 글에서는 프로세스와 스레드, 동기와 비동기에 대한 차이점에 대해 설명드렸습니다😊
다음에는 비동기 스레드의 사용 방법에 대해 궁금해하실 텐데요. 자세한 방법은 후속 콘텐츠에서 다루도록 하겠습니다.
글쓴이 : 오픈마루 연구소
2023년 9월 충청 지역 클라우드 네이티브 세미나 자료 다운로드
/in Cloud, OPENMARU, Seminar, 발표자료/by 주하 원2023년 8월 충청권 공공기관에서 진행된 찾아가는 클라우드 네이티브 세미나
/in Cloud, Seminar, 발표자료/by 주하 원찾아가는 클라우드 네이티브 세미나 – 인천 소재 공공기관
/in Cloud, Seminar, 발표자료, 오픈나루 공지사항/by 주하 원