Ruby는 동적이고 인터프리트 기반의 언어로, 특히 Ruby on Rails 프레임워크를 통해 웹 개발 분야에서 널리 사용됩니다. Ruby의 가장 보편적인 구현체인 MRI(Matz Ruby Interpreter)는 GIL(Global Interpreter Lock)이라는 메커니즘을 사용하는데, 이는 한 번에 오직 하나의 스레드만이 Ruby 코드를 실행하도록 강제하여 '진정한' 병렬 처리에 제약을 둡니다. 하지만 Ruby는 GIL의 존재에도 불구하고 동시성(Concurrency) 및 특정 형태의 병렬성(Parallelism)을 위한 여러 추상화 레벨을 제공합니다. 본 글은 Ruby의 핵심 동시성/병렬성 메커니즘인 프로세스, Ractor, 스레드, Fiber를 심층적으로 분석하여 각자의 특성과 용도를 명확히 이해하는 것을 목표로 합니다. 이들 메커니즘은 Ruby 애플리케이션의 성능과 설계에 중요한 영향을 미치므로, 그 차이점을 파악하는 것은 매우 중요합니다.
Ruby가 제공하는 동시성 및 병렬성 메커니즘은 크게 네 가지 레벨로 나뉩니다. 먼저 프로세스(Process)는 운영체제(OS)에 의해 관리되며, 각 프로세스는 완전히 독립적인 메모리 공간을 가집니다. 따라서 여러 프로세스를 실행하면 OS 스케줄러에 의해 진정한 병렬 처리가 가능합니다. 프로세스 간 통신은 파이프, 소켓, 파일 등 명시적인 IPC(Inter-Process Communication) 메커니즘을 사용해야 합니다. 이는 메모리 격리가 필수적이거나 OS 레벨의 병렬 처리가 필요한 경우에 적합합니다. 이어서 Ractor는 Ruby 3부터 도입된 실험적 기능으로, VM 내에서 관리됩니다. Ractor는 각각 독립적인 메모리 공간과 자체 GIL을 가지므로, 동일 프로세스 내에서 진정한 병렬 처리를 구현할 수 있습니다. Actor 모델과 유사하게 메시지 전달을 통해 데이터를 교환하며, 공유 메모리를 사용하지 않아 Race Condition 발생 위험이 적습니다. Ractor는 주로 CPU 바운드 작업의 병렬 처리에 유용합니다. 반면 스레드(Thread)는 MRI에서 VM에 의해 관리되는 경량 메커니즘입니다. GIL 때문에 한 번에 하나의 Ruby 스레드만 실행 가능하여 CPU 바운드 작업에서는 병렬 효과를 보기 어렵지만, I/O 작업(파일 읽기, 네트워크 통신 등) 중에는 GIL이 해제되어 다른 스레드가 실행될 수 있으므로 I/O 바운드 작업에서는 동시성을 통해 성능 향상을 기대할 수 있습니다. 스레드는 동일한 메모리 공간을 공유하므로 Race Condition을 방지하기 위해 동기화 메커니즘(Mutex 등)이 필요합니다. 마지막으로 Fiber는 가장 경량의 협력적 동시성 메커니즘입니다. 스레드와 달리 OS나 VM에 의해 선점적으로 스케줄링되지 않으며, Fiber.yield
와 Fiber.resume
를 통해 명시적으로 제어권을 주고받습니다. Fiber는 동일한 스레드 내에서 실행되며 메모리를 공유합니다. 저수준 API에 해당하며, 주로 제너레이터(Generator)나 코루틴(Coroutine) 구현에 유용합니다. 이처럼 각 메커니즘은 관리 방식, 메모리 공유 여부, 병렬/동시성 특성에서 명확한 차이를 보입니다. 예를 들어, 스레드 예제 코드에서 공유 메모리(클래스 변수)를 사용할 때 발생하는 Race Condition은 스레드의 메모리 공유 특성 때문에 발생하며, Ractor 예제는 Ractor별 GIL과 메모리 격리 덕분에 병렬 실행이 가능함을 보여줍니다.
종합적으로 볼 때, Ruby는 프로세스, Ractor, 스레드, Fiber라는 다양한 수준의 동시성 및 병렬성 도구를 제공합니다. 프로세스는 완전한 격리와 OS 기반 병렬 처리를, Ractor는 VM 기반의 메모리 격리 병렬 처리를, 스레드는 GIL의 제약 하에서 I/O 바운드 작업에 강점을 보이는 동시성을, Fiber는 명시적 제어를 통한 협력적 동시성을 제공합니다. 따라서 어떤 메커니즘을 사용할지는 애플리케이션의 요구사항, 즉 CPU 바운드 작업인지 I/O 바운드 작업인지, 진정한 병렬 처리가 필요한지 동시성으로 충분한지, 메모리 격리가 필요한지 등에 따라 신중하게 결정해야 합니다. 이러한 이해는 Puma(스레드 우선)와 Unicorn(프로세스 우선)과 같은 웹 서버 선택 논쟁의 맥락을 파악하는 데도 도움을 줍니다. Ruby의 동시성 모델을 제대로 이해하는 것은 효율적이고 안정적인 Ruby 애플리케이션을 개발하는 데 있어 필수적입니다.