LLM 애플리케이션은 스레드 기반 동시성 모델에서 심각한 문제들을 야기합니다. 첫째, 긴 스트리밍 응답으로 인해 워커 스레드가 유휴 상태로 오래 점유되어 ‘슬롯 고갈(Slot Starvation)’이 발생합니다. 둘째, 각 스레드가 별도의 DB 연결, 메모리 등을 요구하여 ‘자원 증식(Resource Multiplication)’과 비효율적인 자원 활용을 초래합니다. 셋째, 스레드 생성 및 컨텍스트 전환의 ‘성능 오버헤드(Performance Overhead)’가 누적되어 지연 시간을 증가시킵니다. 넷째, OS 스케줄러의 한계로 인해 ‘확장성(Scalability Challenges)’에 제약이 생겨 수천 개의 동시 대화 처리가 어렵습니다. 이는 LLM 통신이 기존 백그라운드 작업과 근본적으로 다르기 때문입니다.
루비의 비동기 방식은 파이버(Fibers)를 기반으로 이 문제들을 해결합니다. 스레드가 운영체제에 의해 강제로 중단되는 ‘선점형(Preemptive)’ 방식인 반면, 파이버는 I/O 작업 시 자발적으로 제어권을 양보하는 ‘협력적(Cooperative)’ 방식입니다. 루비의 GVL(Global VM Lock)로 인해 CPU 작업에서 스레드의 병렬성 이점이 제한되는 상황에서, I/O 바운드 작업에 특화된 파이버는 더욱 효율적입니다. 파이버는 하나의 스레드가 epoll
, kqueue
같은 I/O 멀티플렉싱을 통해 수천 개의 I/O 작업을 모니터링하여 ‘하나의 스레드, 여러 I/O 작업’ 모델을 구현합니다. 이는 스레드보다 20배 빠른 할당, 10배 빠른 컨텍스트 전환, 15배 높은 처리량을 가능하게 하며, 사용자 공간에서 관리되어 OS 자원 의존성을 줄이고 뛰어난 확장성을 제공합니다. 결과적으로, 비동기 방식은 LLM의 ‘슬롯 고갈 없음’, ‘자원 공유’, ‘성능 향상’, ‘대규모 확장성’ 문제들을 효과적으로 해결합니다.
루비의 비동기 생태계는 그 투명성이 큰 장점입니다. async
gem을 활용하면 Net::HTTP
와 같은 기존 라이브러리가 자동으로 I/O 작업 시 파이버에 제어권을 양보하여, 별도의 async/await
키워드 없이도 동시성을 확보합니다. 이는 RubyLLM
이 추가적인 코드 변경 없이 비동기 성능을 얻는 이유이기도 합니다. Falcon, async-job, async-cable 등 다양한 비동기 관련 라이브러리들이 존재하며, Rails 애플리케이션의 비동기 마이그레이션 또한 몇 줄의 코드 변경만으로 쉽게 이루어질 수 있습니다. 비동기는 I/O 바운드 작업, API 호출, 스트리밍, LLM 애플리케이션에 특히 적합하며, CPU 집약적 작업 등에는 스레드 사용이 여전히 권장됩니다.