알림: 이 글은 Tuning ZIO for high performance의 한국어 번역본입니다.
안녕하세요, 스튜디오 킹덤에서 서버 소프트웨어 엔지니어로 일하고 있는 삐에르입니다.
Disclaimer
우선, 이 글에서 논의하는 내용이 절대적인 진리는 아니라는 점을 밝힙니다. 무엇을 수행하는 애플리케이션인지에 따라 애플리케이션을 빠르게 만드는 방법도 크게 좌우됩니다. 사용 사례에 따라 ZIO의 오버헤드는 완전히 무시할 수 있는 수준일 수도 있지만, 상당히 클 수도 있습니다. 이를 알 수 있는 유일한 방법은 프로파일러와 함께 부하 테스트를 수행하여 애플리케이션 성능을 분석하고 측정하는 것입니다. 하지만 이 글을 통해 ZIO 앱을 프로덕션에 배포할 때 생각할 법한 몇 가지 흥미로운 점과 고려할 점을 알려드리고자 합니다.
#런타임 플래그
우선 쉬운 것부터 시작하겠습니다. ZIO에는 애플리케이션 실행 시 쉽게 켜거나 끌 수 있는 몇 가지 플래그가 있습니다. 이 중 모든 플래그를 상세히 설명하지는 않겠지만, 프로덕션에서 어느 정도 영향을 미칠 수 있는 두 가지 플래그에 주목하고자 합니다.
첫 번째 플래그는 FiberRoots라는 이름을 가지고 있으며 기본적으로 활성화되어 있습니다. 이 플래그가 활성화되면 루트 fiber가 생성될 때마다(일반적으로 forkDaemon 연산자를 사용할 때) 이 fiber는 모든 루트 fiber를 추적하는 목록에 추가됩니다. 이 메커니즘은 두 가지 목적을 가지고 있습니다:
- Fiber 덤 프를 수행할 수 있습니다. 스레드 덤프와 마찬가지로 애플리케이션 프로세스에
kill -s INFO를 보내면 루트 fiber의 전체 목록, 상태, 현재 수행 중인 작업(실행 중인 함수)을 출력합니다. - 애플리케이션을 중지할 때 ZIO는 모든 루트 fiber를 중단하려고 시도합니다. 이를 추적하지 않으면 메인 fiber에서 생성된 "자식" fiber만 중단되지만,
forkDaemon을 통해 생성된 다른 루트 fiber는 깨끗하게 종료되지 않을 것입니다.
그러나 많은 루트 fiber를 생성하는 경우 이러한 추적에는 비용이 발생할 수 있습니다. 가장 극단적인 사례로, 아무 작업도 하지 않는 루트 fiber만 포크하는 경우 FiberRoots 플래그를 비활성화하면 성능이 2.5배 향상됩니다(벤치마크 결과는 여기 참조). 비록 코드에서 forkDaemon을 많이 사용하지 않더라도 사용하는 라이브러리에 의해 호출될 수 있습니다. 예를 들어, zio-grpc는 모든 gRPC 요청에서 forkDaemon을 호출합니다. 일부 ZIO 연산자도 내부적으로 이를 사용합니다.
개인적으로 로컬 환경에서는 가끔 fiber 덤프를 사용하지만, 프로덕션에서는 정말로 필요하지 않기 때문에 불필요한 추적을 피하기 위해 해당 플래그를 비활성화했습니다. 이를 비활성화하려면 ZIO 앱의 bootstrap 함수에 다음 코드를 추가하면 됩니다:
val bootstrap = Runtime.disableFlags(RuntimeFlag.FiberRoots)
기본적으로 비활성화되어 있는 또 다른 흥미로운 플래그가 있습니다: RuntimeMetrics. 이 플래그를 활성화하면 몇몇 성능 메트릭이 직접 ZIO 메트릭으로 노출됩니다. Prometheus, Datadog 등 원하는 백엔드로 메트릭을 보내고 있다면, 이들은 자동으로 송출될 것입니다. 수집되는 메트릭은 다음과 같습니다:
zio_fiber_failure_causes는 fiber 실패의 개략적인 원인을 수집합니다.zio_fiber_fork_locations는 코드에서 fiber를 가장 많이 포크하는 위치를 알려줍니다.zio_fiber_started,zio_fiber_successes,zio_fiber_failures는 시작된 fiber의 수와 성공하거나 실패한 fiber의 수를 계산합니다.zio_fiber_lifetimes는 fiber가 얼마나 오래 실행되는지를 측정합니다.
프로덕션 시스템을 모니터링할 때 이러한 메트릭은 매우 유용할 수 있으므로 런타임 오버헤드를 감안하더라도 이를 고려해볼만 합니다. 그 오버헤드는 매우 작겠지만, 적용하기 전에 항상 테스트하고 측정해야 한다는 점을 명심해주세요.
이를 활성화하려면 bootstrap 함수에 다음 코드를 추가하면 됩니다:
val bootstrap = Runtime.enableRuntimeMetrics