동시성 마스터하기
여러 사람이 동시에 앱을 사용하고 최대한 빨리 앱을 제공하고 싶습니다. 따라서 동시성을 처리하는 방법이 필요합니다. 두려워하지 마십시오! 대부분의 웹 서버는 이미 기본적으로 이 작업을 수행합니다. 그러나 확장해야 하는 경우 가능한 가장 효율적인 방법으로 동시성을 사용하려고 합니다.
동시성 유형
동시성을 처리하는 방법에는 다중 프로세스, 다중 스레딩 및 이벤트 기반과 같은 여러 가지가 있습니다. 이들 각각은 용도, 장단점이 있습니다. 이 기사에서는 어떻게 다른지, 언제 사용해야 하는지 알아보겠습니다.
다중 프로세스(유니콘)
이것은 동시성을 처리하는 가장 쉬운 방법입니다. 마스터 프로세스는 여러 작업자 프로세스로 분기됩니다. 작업자 프로세스는 실제 요청을 처리하고 마스터는 작업자를 관리합니다.
헤더>각 작업자 프로세스는 메모리에 전체 코드베이스를 가지고 있습니다. 이로 인해 이 방법은 메모리 집약적이며 더 큰 인프라로 확장하기 어렵습니다.
다중 프로세스 요약 | |
---|---|
사용 사례 | 아마도 당신이 알고 있는 비-루비의 예는 크롬 브라우저입니다. 다중 프로세스 동시성을 사용하여 각 탭에 고유한 프로세스를 제공합니다. 전체 응용 프로그램을 중단하지 않고도 단일 탭이 충돌할 수 있습니다. 이들의 경우 익스플로잇을 단일 탭으로 격리하는 데 도움이 됩니다. |
장점 | 구현이 가장 간단합니다. 스레드 안전 문제를 무시합니다. 각 작업자는 시스템의 나머지 부분을 손상시키지 않고 충돌할 수 있습니다. |
단점 | 각 프로세스는 전체 코드베이스를 메모리에 로드합니다. 이로 인해 메모리 집약적입니다. 따라서 많은 양의 동시 연결로 확장되지 않습니다. |
멀티 스레딩(퓨마)
이 스레딩 모델을 사용하면 한 프로세스가 동시에 여러 요청을 처리할 수 있습니다. 단일 프로세스 내에서 여러 스레드를 실행하여 수행합니다.
헤더>다중 프로세스 접근 방식과 달리 모든 스레드는 동일한 프로세스 내에서 실행됩니다. 즉, 전역 변수와 같은 데이터를 공유합니다. 따라서 스레드당 작은 추가 메모리 청크만 사용됩니다.
글로벌 통역 잠금
이것은 MRI의 글로벌 인터프리터 락(GIL)을 제공합니다. GIL은 모든 Ruby 코드 실행에 대한 잠금입니다. 스레드가 병렬로 실행되는 것처럼 보이지만 한 번에 하나의 스레드만 활성화됩니다.
IO는 GIL 외부에서 작동합니다. 결과가 돌아올 때까지 기다리는 데이터베이스 쿼리를 실행하면 잠기지 않습니다. 다른 스레드는 그 동안 일부 작업을 수행할 기회가 있습니다. 스레드의 해시 또는 배열에 대해 많은 수학 및 연산을 수행하는 경우 MRI를 사용하는 경우 단일 코어만 활용합니다. 대부분의 경우 시스템을 완전히 활용하려면 여전히 여러 프로세스가 필요합니다. 또는 GIL이 없는 Rubinius 또는 jRuby를 사용할 수 있습니다.
스레드 안전성
여러 스레드를 사용하는 경우 스레드로부터 안전한 방식으로 공유 데이터를 조작하는 모든 코드를 작성하는 데 주의해야 합니다. 예를 들어 Mutex를 사용하여 공유 데이터 구조를 조작하기 전에 잠그면 이를 수행할 수 있습니다. 이렇게 하면 데이터를 변경하는 동안 다른 스레드가 오래된 데이터를 기반으로 작업하지 않도록 할 수 있습니다.
다중 스레드 요약 | |
---|---|
사용 사례 | 이것은 "중간" 옵션입니다. 짧은 요청 로드를 처리해야 하는 많은 표준 웹 애플리케이션(예:바쁜 웹 애플리케이션)에 사용됩니다. |
장점 | 다중 프로세스보다 적은 메모리를 사용합니다. |
단점 | 코드가 스레드로부터 안전한지 확인해야 합니다. 스레드가 충돌을 일으키면 잠재적으로 프로세스가 중단될 수 있습니다. GIL은 I/O를 제외한 모든 작업을 잠급니다. |
이벤트 루프(씬)
이벤트 루프는 많은 동시 I/O 작업을 수행해야 할 때 사용됩니다. 모델 자체는 동시에 여러 요청을 강제로 실행하지 않지만 많은 동시 사용자를 처리하는 효율적인 방법입니다.
헤더>
아래에서 Ruby로 작성된 매우 간단한 이벤트 루프를 볼 수 있습니다. 루프는 event_queue
에서 이벤트를 가져옵니다. 처리합니다. 이벤트가 없으면 대기열에 새 이벤트가 있는지 확인하기 위해 대기하고 반복합니다.
loop do
if event_queue.any?
handle_event(event_queue.pop)
else
sleep 0.1
end
end
그림 버전
이 그림에서는 한 단계 더 나아가고 있습니다. 이벤트 루프는 이제 OS, 대기열 및 약간의 메모리와 함께 멋진 춤을 춥니다.
단계별
- OS는 네트워크 및 디스크 가용성을 추적합니다.
- OS가 I/O가 준비되었음을 확인하면 이벤트를 대기열로 보냅니다.
- 대기열은 이벤트 루프가 맨 위에 있는 이벤트 목록입니다.
- 이벤트 루프가 이벤트를 처리합니다.
- 일부 메모리를 사용하여 연결에 대한 메타 데이터를 저장합니다.
- 새 이벤트를 직접 이벤트 큐에 다시 보낼 수 있습니다. 예를 들어 이벤트 내용에 따라 대기열을 종료하라는 메시지입니다.
- I/O 작업을 수행하려는 경우 특정 I/O 작업에 관심이 있음을 OS에 알립니다. OS는 네트워크와 디스크를 추적하고([1] 참조) I/O가 준비되면 이벤트를 다시 추가합니다.
이벤트 루프 요약 | |
---|---|
사용 사례 | 사용자에게 많은 동시 연결을 사용하는 경우. Slack과 같은 서비스를 생각해 보십시오. 크롬 알림. |
장점 | 연결당 메모리 오버헤드가 거의 없습니다. 많은 수의 병렬 연결로 확장됩니다. |
단점 | 이해하기 어려운 멘탈 모델입니다. 배치 크기는 작아야 하고 대기열이 쌓이지 않도록 예측 가능해야 합니다. |
어느 것을 사용해야 합니까?
이 기사를 통해 다양한 동시성 모델에 대해 더 잘 이해할 수 있기를 바랍니다. 개발자로서 이해하기 더 어려운 주제이지만, 이를 이해하면 앱에 적합한 설정을 실험하고 사용할 수 있는 도구를 얻을 수 있습니다.
헤더>요약
- 대부분의 앱에서 스레딩이 의미가 있기 때문에 Ruby/Rails 생태계는 (천천히) 이런 식으로 움직이는 것 같습니다.
- 장시간 실행되는 스트림으로 동시성이 높은 앱을 실행하는 경우 이벤트 루프를 사용하여 확장할 수 있습니다.
- 트래픽이 많은 사이트가 없거나 작업자가 오래된 다중 프로세스를 중단할 것으로 예상되는 경우
그리고 스레드 내부, 다중 프로세스 설정 내부에서 이벤트 루프를 실행할 수 있습니다. 네, 스트룹 와플을 가지고 먹을 수 있습니다!
이러한 동시성 모델에 대해 자세히 알아보려면 다중 프로세스, 다중 스레딩 및 이벤트 루프에 대한 자세한 기사를 확인하세요.