다중 스레드 환경에서 공유 상태 관리의 문제점은 여러 예시를 통해 명확히 드러납니다.
공유 상태 문제점의 예시
- 파일 분할 서비스: 클래스 레벨 메서드에서 파일을 분할하고 결과를 클래스 변수에 저장할 경우, 여러 요청이 동시에 처리되면 다른 요청의 데이터가 덮어씌어지는 문제가 발생합니다.
- 페이지네이션 인덱싱: 유사하게, 다중 스레드 시스템에서 컨텍스트 스위치가 발생하면 클래스 변수에 저장된 인덱스 값이 예상과 다르게 변경되어 오류를 일으킬 수 있습니다.
- 과거의 실패 경험: 발표자는 과거에 주문 처리 시스템에서 자체적인 동기화 로직(별도 프로세스 및 스레드 활용)을 구현했으나, 이는 클래스 변수를 사용한 “매우 나쁜 코드”의 전형적인 예시로, 유지보수와 디버깅에 큰 어려움을 겪었음을 고백합니다.
공유 상태 관리의 대안과 한계
- Mutex: 상호 배제를 통해 동시 접근을 막는 기본적인 방법이지만, 구현의 복잡성과 잠재적인 버그를 유발할 수 있습니다. 발표자는 Mutex 사용 경험이 “끔찍했다”고 언급합니다.
- Rack::Lock 미들웨어: 전체 요청을 단일 Mutex로 감싸 동시성을 제한합니다. 이는 전체 애플리케이션의 성능을 심각하게 저하시키며, 다중 스레드 기반의 Puma와 같은 서버의 이점을 상쇄합니다.
- Thread Local Variables (
Thread.current
): 스레드별로 독립적인 데이터를 저장할 수 있어 공유 상태 문제를 해결하는 것처럼 보입니다. 그러나 Puma와 같은 서버는 스레드를 재사용하므로, 요청 처리 후 스레드 로컬 변수를 명시적으로 초기화(nil
로 설정)하지 않으면 이전 요청의 데이터가 다음 요청으로 유출될 수 있습니다. 또한,ApplicationController
에서 상태를 관리하는 것은 비즈니스 로직과 상태 관리의 결합을 야기하여 코드의 응집도를 떨어뜨립니다.
ActiveSupport::CurrentAttributes의 활용
Rails 5.2부터 도입된 ActiveSupport::CurrentAttributes
는 이러한 문제에 대한 개선된 접근 방식을 제공합니다.
* 기능: Thread.current
와 유사하게 스레드 레벨 저장소에 상태를 저장하지만, Rails 요청 생명주기(Request Cycle)에 자동으로 연결되어 요청 시작 시 설정되고 요청 종료 시 초기화됩니다.
* 활용: CurrentAttributes
를 상속받는 클래스를 정의하고, 필요한 속성(예: username
, locale
)을 지정하여 사용합니다. set
메서드를 통해 요청 사이클 외부(예: 백그라운드 작업)에서도 사용하거나, with
블록을 통해 임시로 값을 오버라이드할 수 있습니다.
* 격리 수준: 기본적으로 스레드 기반 격리를 사용하며, 필요에 따라 파이버 기반 격리(scope: :fiber
)도 지원합니다. 이는 Thread.current
또는 Fiber.current
를 사용하여 데이터를 저장하는 방식입니다.
CurrentAttributes의 한계점
- 전역 상태의 본질:
CurrentAttributes
는 내부적으로 여전히 전역 상태(Singleton 인스턴스)를 관리하며, 이는 “마법”처럼 작동하지만, 여러 곳에서 속성을 설정할 경우 “블랙 매직”이 되어 디버깅을 어렵게 만들 수 있습니다. - 서브 파이버 문제: 파이버 기반 서버(예: Falcon)나
async
gem을 사용하는 환경에서fiber
격리 수준을 설정하더라도, 서브 파이버는 부모 파이버의 데이터를 자동으로 상속받지 못하는 문제가 있습니다. Ruby 3.x에서 도입된Fiber.storage
가 이 문제의 해결책으로 제시됩니다. - 작업 간 데이터 전달 불가: 백그라운드 작업(Job) 간에는
CurrentAttributes
를 통해 데이터를 전달할 수 없으므로, ID와 같은 필수 정보를 명시적으로 전달해야 합니다. - “명시적인 것이 암시적인 것보다 낫다”: 발표자는 Ryan Biggs의 의견에 동의하며,
CurrentAttributes
의 암시적인 특성보다는Devise
의current_user
와 같이 명시적인 메서드를 통한 접근이 테스트 용이성 및 예측 가능성 측면에서 더 안전할 수 있다고 강조합니다. 모델 내부에서 요청 관련 정보를 필요로 하는 경우,CurrentAttributes
는 편리할 수 있으나, 콘솔이나 테스트 환경에서의 설정 복잡성으로 인해 취약할 수 있습니다.