루비 에이전트는 매우 오랫동안 실행되거나 끝나지 않는 트랜잭션을 트레이스하도록 설계되지 않았습니다. 이 가이드는 이로 인해 메모리가 증가할 수 있는 이유를 설명하고 이를 해결하기 위한 전략을 제공합니다.
문제
애플리케이션의 메모리 사용량이 계속 증가하며, 이는 오랫동안 열려 있는 하나 이상의 트랜잭션과 상관관계가 있습니다. 이는 주로 다음과 같은 경우에 발생합니다:
- 대규모 배치를 처리하거나 많은 데이터베이스 쿼리 또는 외부 호출을 실행하는 등 오랫동안 하나의 트랜잭션에 머무르는 백그라운드 작업 또는 워커
- 프로세스의 수명 동안 유지되며 에이전트가 하나의 지속적으로 실행되는 트랜잭션으로 취급하는 스레드
- 평소보다 훨씬 오래 실행되거나 전혀 완료되지 않는 트랜잭션
발생하는 이유
트랜잭션에서 트레이스된 모든 작업 단위에 대해 에이전트는 세그먼트를 생성하여 트랜잭션 트레이스를 구축하고 각 세그먼트 상위 항목의 전용 시간을 계산할 수 있도록 합니다. transaction_tracer.limit_segments 설정(기본값 4000)은 트랜잭션 트레이스에 추가되는 이러한 세그먼트의 수를 제한합니다.
그러나 해당 한도에 도달한 후에도 에이전트는 상위 세그먼트의 독점 시간 계산을 정확하게 유지하기 위해 추가 작업 단위마다 세그먼트를 계속 생성하고 시작 및 종료 시간을 추적합니다. 계속 실행되는 트랜잭션의 경우, 이 타이밍 데이터는 트랜잭션이 열려 있는 동안 계속 누적됩니다. 이는 트랜잭션이 완료될 때까지 해당 트랜잭션에 연결된 메모리가 해제되지 않음을 의미하며, 장기 실행되거나 끝나지 않는 트랜잭션의 경우 매우 오랜 시간이 걸릴 수 있습니다.
솔루션
설정 옵션으로 메모리 증가 제한
아래 옵션 중 어느 것도 트랜잭션을 단축하지는 않지만, 트랜잭션이 열려 있는 동안 에이전트가 보관하는 데이터의 양을 제한할 수 있습니다.
트레이스 한도에 도달하면 세그먼트 타이밍 데이터를 제한합니다. 장기 실행 트랜잭션이
transaction_tracer.limit_segments에서 허용하는 것보다 더 많은 세그먼트를 생성할 것으로 예상되는 경우,transaction_tracer.cap_segment_artifacts을(를) 활성화하십시오(에이전트 버전 10.7.0 이상에서 사용 가능, 기본적으로 비활성화됨).세그먼트 한도에 도달하면 에이전트가 해당 트랜잭션의 추가 세그먼트에 대한 독점 시간 데이터를 기록하는 것을 중지하며, 이는 트랜잭션의 메모리 증가를 제한합니다. 세그먼트 타이밍 수집을 중지하므로, 그에 대한 트레이드오프로 트랜잭션 내 상위 세그먼트 타이밍 데이터의 정확도가 떨어집니다.
세그먼트 제한 자체를 낮추십시오. 단일 트랜잭션 트레이스에 수천 개의 노드가 필요하지 않은 경우
transaction_tracer.limit_segments(기본값4000)을 낮추면 에이전트가 트레이스에 새 세그먼트를 추가하는 것을 더 빨리 중지합니다. 트랜잭션이 실제로 이 제한에 도달하는지 확인하려면 디버그 수준 로깅을 켜고Segment limit of [segment_limit] reached, ceasing collection.을(를) 찾으십시오.스팬 이벤트 볼륨을 줄이십시오. 완료되는 각 세그먼트는 트랜잭션 트레이스와 독립적으로 스팬 이벤트를 생성합니다. 비정상적으로 많은 수의 세그먼트를 생성하는 트랜잭션의 경우,
span_events.max_samples_stored(기본값2000)을 낮추거나, 스팬 수준의 분산 추적 세부 정보가 필요하지 않다면span_events.enabled: false을(를) 사용하여 앱의 스팬 이벤트를 완전히 비활성화하십시오. 이는 메모리에 영향을 미치는 한 가지 요인을 줄여주지만, 위에서 설명한 근본적인 세그먼트/타이밍 누적을 막지는 못하므로 해결책이라기보다는 부분적인 완화책으로 간주하십시오.Sidekiq 작업이 웹 트랜잭션을 부풀리는 것을 방지하십시오. 장기 실행 트랜잭션이 실제로 요청 내에서 Sidekiq 작업을 실행하기 때문에 오래 실행되는 웹 트랜잭션인 경우, 작업의 수행 내용은 기본적으로 해당 웹 트랜잭션 내의 중첩된 세그먼트가 되므로 느리거나 세그먼트가 많은 작업은 웹 트랜잭션의 기간을 늘리고 세그먼트 수를 증가시킵니다. 에이전트가 작업이 시작되는 즉시 웹 트랜잭션을 완료하고 대신 작업을 자체 트랜잭션으로 기록하도록
sidekiq.separate_transactions(에이전트 버전 10.4.0 이상에서 사용 가능, 기본적으로 비활성화됨)을 활성화하십시오.수명이 긴 스레드에 대한 자동 추적을 해제하십시오. 단일 작업이 아닌 프로세스의 수명 동안 유지되는 스레드에서 증가가 발생하는 경우,
instrumentation.thread.tracing을(를) 비활성화하여 에이전트가 스레드를 자동으로 계측하는 것을 중지할 수 있습니다. 모든 스레드 추적을 비활성화하지 않으려면, 단일 스레드를NewRelic::Agent.disable_all_tracing(으)로 래핑하여 해당 단일 스레드에 대한 추적을 해제할 수 있습니다.미들웨어에서 세그먼트 수를 줄이십시오. 대규모 타사 Rack 또는 Rails 미들웨어 스택이 있는 웹 트랜잭션의 경우,
disable_middleware_instrumentation은(는) 에이전트가 각 미들웨어를 자체 세그먼트로 래핑하는 것을 중지합니다.
커스텀 측정 사용
트랜잭션을 분할하십시오. 긴 트랜잭션의 경우, 커스텀 측정을 사용하여 트랜잭션 내의 각 작업 단위를 자체적인 짧은 트랜잭션으로 계측하는 것을 고려할 수 있습니다. 각 트랜잭션의 세그먼트 데이터는 하나의 트랜잭션이 진행되는 전체 기간 동안 누적되는 대신, 해당 트랜잭션이 완료되는 즉시 기록되고 해제됩니다.
NewRelic::Agent::Tracer.in_transaction사용:require 'new_relic/agent/tracer'def process_large_batch(items)items.each do |item|NewRelic::Agent::Tracer.in_transaction(partial_name: 'Custom/process_item', category: :task) doprocess_item(item)endendend이를 통해 메트릭, 트레이스 및 오류 보고가 예상대로 작동하도록 유지하는 동시에, 계속해서 증가하는 하나의 트랜잭션을 수명이 짧은 여러 트랜잭션으로 대체합니다.
작업 기간 동안 세그먼트 누적을 완전히 중지하십시오. 장기 실행 작업의 본문을
NewRelic::Agent.disable_all_tracing(으)로 래핑하면 블록 내부에서 수행된 작업이 트랜잭션에 연결되지 않으므로 위에서 설명한 누적이 완전히 중지됩니다:def perform(*args)NewRelic::Agent.disable_all_tracing dodo_the_long_running_work(*args)endend트레이드오프는 블록 내부의 모든 항목에 대한 계측 세부 정보를 모두 잃는다는 것입니다. 이러한 트레이드오프가 가치 있는지는 작업에 대해 얼마나 많은 가시성이 필요한지에 따라 달라집니다.
OpenTelemetry로 장기 실행 작업 계측
위의 옵션을 적용한 후에도 에이전트의 트랜잭션 모델에 적합하지 않은 코드의 경우, OpenTelemetry 루비 SDK로 해당 코드 부분을 계측하고 OTLP를 통해 뉴렐릭으로 내보내는 것을 고려하십시오. 작업의 전체 기간 동안 데이터를 유지하는 동일한 장기 실행 트랜잭션 객체를 갖는 대신, OpenTelemetry는 각 스팬이 완료되는 즉시 내보냅니다.
중요
이는 루비 에이전트의 OpenTelemetry API 지원과 다릅니다. 해당 기능은 OpenTelemetry API 호출을 에이전트 자체의 트랜잭션 및 세그먼트 모델로 변환하므로, 위에서 설명한 것과 동일한 메모리 동작의 영향을 받습니다. 자체 OTLP 익스포터와 함께 독립 실행형 OpenTelemetry SDK를 사용하면 에이전트의 트랜잭션/세그먼트 추적을 완전히 피할 수 있습니다.
자세한 내용은 OpenTelemetry 및 뉴렐릭 소개를 참조하십시오.