기본 콘텐츠로 건너뛰기

[Opencensus - Trace] Opencensus를 활용하는 Trace 정리

Trace 정리

Tracing (이하 트레이싱)

추적은 응용 프로그램을 구성하는 다른 서비스에 의해 처리되는 단일 사용자 요청의 진행과정을 추적하는 것이다.

진행 과정의 각 작업은 Span 으로 부르며, 각 스팬에는 단계에 소요된 시간 (대기 시간), 상태, 시간 이벤트, 속성, 링크를 포함하여 작업을 나타낼 수 있는 메타 데이터가 포함된다. 이 추적 정보를 활용해서 애플리케이션의 오류 및 대기 시간 문제를 디버깅 할 수 있다.

Trace (이하 추적)

추적은 스팬 트리로 구성된다. 즉, 작업의 흐름 경로를 보여주는 관찰 가능한 신호의 집합이다. 추적 자체는 고유한 16바이트 시퀀스로 표현되는 TraceID로 식별된다. 트레이싱을 통해서 수집된 추적 정보는 다음과 같이 표현된다.

Trace

위의 그림에서와 같이 여러 단계의 작업(스팬)이 호출된 것을 확인할 수 있다.

  • auth: 사용자 인증 여부 검사
  • cache.Get: 캐시 검증
  • mysql.Query: 캐시에 존재하지 않아 DB 조회
  • cache.Put: DB 조회결과 캐시 처리

Exporting (이하 내보내기)

수집된 추적 정보를 다양한 분석을 위해서 여러 가지 백엔드들에 대한 내보내기를 구성해서 처리할 수 있다.
다양한 내보내기 목록등은 Opencensus 를 참고하면 된다.

Span (이하 스팬)

스팬은 추적의 단일 작업을 나타낸다. 주로 HTTP 요청, RPC (원격 프로시저 호출: Remote Procedure Call), 데이터베이스 쿼리 또는 코드가 사용자 코드에서 사용하는 경로 등을 나타낸다.

  • 스팬은 트리 구조를 이루기 때문에 상위 스팬의 존재여부에 따라서 상위 스팬과 하위 스팬으로 나뉜다.
  • 각 스팬은 SpanID로 식별된다.
  • SpanID 와 옵션 바이트들을 합쳐서 Span Context라고 한다.
  • 동일 프로세스 내에서 스팬 컨텍스트는 컨텍스트 객체로 전달된다. 프로세스 경계를 넘어서면 프로토콜 헤더로 직렬화 된다. 따라서 수신 측은 스팬 컨텍스트를 읽고 하위 스팬을 만들 수 있다.

스팬은 다음과 같은 필드 정보로 구성된다.

  • Name

    • 작업을 설명할 수 있는 문자열, 일반적으로 통계적으로 의미가 있을 수 있는 이름을 사용하는 것이 좋다. 백엔드 및 분석 도구가 이 이름을 기준으로 보고서를 생성한다.

    • 스팬 생성자에서 지정한다.

      cox, span := trace.StartSpan(ctx, "cache.Get")
  • SpanID

    • 스팬 식별자
    • 무작위로 생성된 8바이트 구성이며 전역적으로 고유한 값이어야 한다.
    • 하위 스팬이 존재할 경우 ParentSpanID로 지정된다. ParentSpanID 가 null인 경우는 최상위 스팬으로 판단한다.
  • TraceID

    • 추적 식별자
    • 무작위로 생성된 16바이트 구성이며 전역적으로 고유한 값이어야 한다.
    • 모든 프로세스에서 동일한 추적에 속하는 모든 스팬들을 그룹화하는 기준이다.
  • ParentSpanID

    • 현재 작업 단위 스팬의 상위 스팬 여부를 의미한다.
    • null이거나 SpanID 값이 존재할 수 있다.
    • 동일한 ParentSpanID를 가지는 하위 스팬들이 존재할 수 있지만, 하나의 스팬은 하나의 ParentSpanID만 가질 수 있다.
  • StartTime / EndTime

    • StartTime은 작업이 시작된 시간을 기록하는 타임스탬프 값이다.
    • EndTime은 작업이 종료된 시간을 기록하는 타임스탬프 값이다.
    • 처리(지연) 시간은 EndTime과 StartTime 의 차이를 의미한다.
    • 스팬의 수명은 스팬 객체에 시작 시간과 종료 시간을 기록하는 프로세스를 나타낸다.
      • 시작 시간은 스팬이 생성될 떄 기록되며, 시작 시간이 기록된 경우만 살아있다.
      • 종료 시간은 작업의 종료 시간으로 기록하며, 추적이 종료된 후에 스팬을 종료하는 것이 중요하다.
  • Status

    • 특정 시점에 스팬의 필터링 가능한 상태를 나타내는 논리적 모델을 정의한다.
    • int32 코드로 구성된다.
    • 상태 코드 매핑된 정보는 다음과 같다.
    STATE CODE DESCRIPTION HTTP STATUS CODE EQUIVALENT
    OK 0 Not an error, returned on success 200 and 2XX HTTP statuses
    CANCELLED 1 The operation was cancelled, typically by the caller 499
    UNKNOWN 2 An unknown error raised by APIs that don’t return enough error information 500
    INVALID_ARGUMENT 3 The client specified an invalid argument 400
    DEADLINE_EXCEEDED 4 The deadline expired before the operation could succeed 504
    NOT_FOUND 5 Content was not found or request was denied for an entire class of users 404
    ALREADY_EXISTS 6 The entity attempted to be created already exists 409
    PERMISSION_DENIED 7 The caller doesn’t have permission to execute the specified operation 403
    RESOURCE_EXHAUSTED 8 The resource has been exhausted e.g. per-user quota exhausted, file system out of space 429
    FAILED_PRECONDITION 9 The client shouldn’t retry until the system state has been explicitly handled 400
    ABORTED 10 The operation was aborted 409
    OUT_OF_RANGE 11 The operation was attempted past the valid range e.g. seeking past the end of a file 400
    UNIMPLEMENTED 12 The operation is not implemented or is not supported/enabled for this operation 501
    INTERNAL 13 Some invariants expected by the underlying system have been broken. This code is reserved for serious errors 500
    UNAVAILABLE 14 The service is currently available e.g. as a transient condition 503
    DATA_LOSS 15 Unrecoverable data loss or corruption 500
    UNAUTHENTICATED 16 The requester doesn’t have valid authentication credentials for the operation 401
    span.SetStatus(trace.Status{Code: int32(trace.StatusCodeNotFound), Message: "Cache miss"})
  • Time events

    • 일정 기간 동안 특정 시점에 발생한 이벤트를 설명한다. 아래와 같은 필드 중에 하나를 사용할 수 있지만 모두 사용할 수는 없다.

      • 주석 : 일정 기간 동안 발생한 이벤트에 대해 텍스트로 설명하는 스토리지 제공

        • Description: 이벤트를 설명하는 사용자 메시지

        • Attributes: 주석을 표현하는데 필요한 속성들

              import "go.opencensus.io/trace"
              ...
              span.Annotation([]trace.Attribute{
                  trace.StringAttribute("store", "memcache"),
                  trace.BoolAttribute("cache_miss", true),
                  trace.Int64Attribute("age_ns", 13488999),
              }, "Cache miss durtin GC")
      • 메시지 이벤트 : 스팬 간에 주고 받은 메시지 설명

        • 유형 (Type)

          유형 기술
          SEND 1 이 메시지가 전송되었음을 나타냅니다.
          RECEIVE 2 이 메시지가 수신되었음을 나타냅니다.
          UNKNOWN 0 알 수없는 이벤트 유형 또는 기본값
        • 신분증명 (ID) : SEND/RECEIVE 간의 메시지 이벤트 상관 관계를 위한 메시지 ID로 예를 들어 프로토콜 핸드 셰이크 또는 스트리밍 RPC간에 시퀀스/상태 번호를 일치시킬 떄 유용할 수 있다.

        • 압축되지 않은 크기 (Uncompressed Size) : 송/수신 상에 압축되지 않은 바이트 수

        • 압축된 크기 (Compressed Size) : 송/신 상에 압축된 바이트 수, 0 이면 압축되지 않은 바이트 수와 동일한 것으로 간주

          // On the client
          span.AddMessageReceiveEvent(seqNumber, 1024, 512)
          // On the server
          span.AddMessageSendEvent(seqNumber, 1024, 512)
    • 시간 이벤트의 모음 이지만, 삭제 된 주석 수와 삭제된 메시지 이벤트 수에 대한 정보도 관리한다.

      • 시간 이벤트 모음
      • 삭제된 주석 수
      • 삭제된 메시지 이벤트 수
  • Link

    • 동일하거나 다른 추적에 속하는 스팬 간의 상호 관계를 설명한다. 예를 들어 서로 다른 추적 또는 서로 다른 프로세스로 구성된 일괄 작업이 수행된 경우에 한 스팬에서 다른 스팬으로의 링크는 관련 스팬들을 상호 연결하는데 도움이 될 수 있다.

    • 다음과 같은 필드로 구성된다.

      • TraceID
      • SpanID
      • 유형
        • CHILD
        • PARENT
        • UNKNOWN
      • Attributes
      # SpanA : 신뢰할 수 없는 경계 (예, 클라우드에 대한 클라이언트 요청)에서 시작됨
      # SpanB : 신뢰할 수 있는 경계 (예, 서비스/클라우드 프런트엔드 서버)에서 시작됨
      

      _, spanA := trace.StartSpan(context.Background(), “SpanA”) spanASC := spanA.SpanContext()

      _, spanB := trace.StartSpan(context.Background(), “SpanB”) spanB.AddLink(trace.Link{ TraceID: spanASC.TraceID, SpanID: spanASC.SpanID, Type: trace.LinkTypeChild, Attributes: map[string]interface{}{ “reason”: “client-RPC unverified source”, } })

  • SpanKind

    • 스팬 간의 상/하위 관계 뿐만 아니라 추가적인 관계를 설명하는데 사용한다.

      유형 의미
      SERVER 1 범위는 RPC의 서버 측 처리를 다룹니다.
      CLIENT 2 범위는 RPC의 클라이언트 측 처리를 포함합니다.
      UNSPECIFIED 0 지정되지 않음
      // Started on the client
      ctx, cSpan := trace.StartSpan(ctx, "SpanStarted", trace.WithSpanKind(trace.SpanKindClient))
      // Received from the server
      ctx, sSpan := trace.StartSpan(ctx, "SpanStarted", trace.WithSpanKind(trace.SpanKindServer))
  • TraceOptions

    TraceOptions는 각 Opencensus 스팬의 바이트로 마지막 비트는 스팬이 샘플링된 경우에 설정된다.

    16 진수 상태 바이너리 상태 의미
    0x00 00000000 스팬이 샘플링되지 않았습니다.
    0x01 00000001 스팬이 샘플링 됨
  • Tracestate

    여러 분산 추적 그래프에서 위치 / 순서에 대한 정보를 설정하기 위한 것으로 최대 32개의 정렬된 목록 사용이 가능한 Key-Value 쌍이다.

    필드 기술 제한
    캐릭터 모음 소문자로 시작해야하며 소문자 영숫자, 대시, 별표 및 슬래시를 포함 &할 수 있습니다.
    캐릭터 모음 인쇄 가능한 ASCII 문자 만

Sampling (이하 샘플링)

견본 추출

추적 데이터는 대량으로 생성되는 경우가 많기 때문에 수집 및 저장 비용뿐만 아니라 전송 비용도 많이 소비된다. 따라서 관찰 가능성과 비용 간의 균형을 맞춰서 추적을 샘플링하는 것이 필요하다. 즉, 전체 범위 중에 추적 대상을 정하는 범위와 내보낼지 여부를 결정하는 프로세스라고 생각하면 된다.

샘플러

  • Always : 모든 샘플링 결정에 true를 반환한다.

     import "go.opencensus.io/traces"
    

    _ = trace.AlwaysSample()

  • Never : 모든 샘플링 결정에 false를 반환한다.

      import "go.opencensus.io/traces"
    

    _ = trace.NeverSample()

  • Probabilistic : 동전 던지기와 같이 샘플링 결정에 true/false를 확률적으로 반환한다. (기본적으로 확률적 샘플링은 10,000분의 1이다)

      import "go.opencensus.io/traces"
    

    theSampler = trace.ProbabilitySampler(1/1000.0)

  • RateLimiting : 초당 0.1개의 추적 샘플링을 시도한다. 만일 상위 스팬이 샘플링되었다면 하위 스팬은 샘플링을 유지한다. 이와 같은 샘플링을 사용하는 이유는 아래와 같은 문제들을 해결하기 위한 용도로 사용한다.

    • QPS 기반 샘플링 얻기

    • 실제 샘플링 확률 제공

    • 최소한의 오버헤드

      // 속도 제한을 달성하기 위해서 마지막 QPS 기반 샘플링 결정을 내린 시간이 원자 변수 (Atomic Variable)에 저장되고 마지막 확률적 결정을 내린 이후의 시간도 저장된다. 그리고 원하는 샘플링 QPS를 얻기 위한 확률 함수를 사용한다.
      

      P(Z) = min(Z * X, 1) // X : 원하는 QPS, Z : 마지막 샘플링 이후 경과된 시간 (초)

샘플러의 결정은 샘플러가 각각 true 또는 false를 반환하는 경우에 설정하거나 지워 Span.TraceOptions의 샘플링 비트 에 영향을 주게 된다.

샘플러 설정

  • 글로벌 샘플러 : TraceConfig 설정을 통해서 전역으로 적용되는 샘플러를 말한다.

      package main
    

    import “go.opencensus.io/traces

    func main() { // Having already created your sampler “theSampler” trace.ApplyConfig(trace.Config{DefaultSampler: theSampler}) }

  • 스팬별 샘플러 (스팬 범위) : 스팬을 생성할 때 지정해서 스팬에 적용되는 샘플러를 말한다.

      import "go.opencensus.io/traces"
    

    func doWork() { // Having already created your sampler “theSampler” and “ctx” ctx, span := trace.StartSpan(ctx, “DoWork”, trace.WithSampler(theSampler)) }

규칙

ParentSpan의 샘플링 결정은 추적 구성과 관련 없이 항상 모든 항목에 상속된다. 이를 통해서 추적의 연속성이 보장된다. 예를 들어 부모가 샘플링 되었는데 자식이 샘플링이 되지 않거나, 자식이 샘플링이 되었는데 부모가 샘플링이 되지 않는 경우들이 발생하는 것을 방지하기 위한 것이다.

참고자료

댓글

이 블로그의 인기 게시물

OData 에 대해서 알아보자.

얼마 전에 어떤 회사에 인터뷰를 하러 간 적이 있었다. 당시 그 회사는 자체 솔루션을 개발할 기술인력을 찾고 있었고 내부적으로 OData를 사용한다고 했다. 좀 창피한 이야기일 수도 있지만 나름 기술적인 부분에서는 많은 정보를 가지고 있다고 했던 것이 무색하게 OData란 단어를 그 회사 사장님에게서 처음 들었다. 작고, 단순한 사이트들만을 계속해서 작업을 하다 보니 어느덧 큰 줄기들을 잃어버린 것을 느끼기 시작했다. 명색이 개발이 좋고, 기술적인 기반을 만들려고 하는 인간이 단어조차도 모른다는 것은 있을 수 없는 것이라서 다시 새로운 단어들과 개념들을 알아보는 시간을 가지려고 한다. OData (Open Data Protocol) 란? 간단히 정리하면 웹 상에서 손쉽게 데이터를 조회하거나 수정할 수 있도록 주고 받는 웹(프로토콜)을 말한다. 서비스 제공자 입장에서는 웹으로 데이터를 제공하는 방식으로 각 포탈 사이트들이 제공하는 OPEN API 포맷을 독자적인 형식이 아니라 오픈된 공통규약으로 제공 가능하며, 개발자는 이 정보를 다양한 언어의 클라이언트 라이브러리로 어플리케이션에서 소비할 수 있도록 사용하면 된다. 공식 사이트는 www.odata.org 이며 많은 언어들을 지원하고 있다. 좀더 상세하게 정의를 해 보면 OData는 Atom Publishing Protocol  (RFC4287) 의 확장 형식이고 REST (REpresentational State Transfer) Protocol 이다. 따라서 웹 브라우저에서 OData 서비스로 노출된 데이터를 볼 수 있다. 그리고 AtomPub 의 확장이라고 했듯이 데이터의 조회만으로 한정되는 것이 아니라 CRUD 작업이 모두 가능하다. Example 웹 브라우저에서 http://services.odata.org/website/odata.svc 를 열어 보도록 하자. This XML file does not appear to have any style in...

C# 에서 Timer 사용할 때 주의할 점.

예전에 알고 지내시던 분의 질문을 받았다. Windows Forms 개발을 하는데, 주기적 (대략 1분)으로 데이터 요청을 하는 프로그램을 작성하기 위해서 Timer 를 사용하는데, 어떤 기능을 처리해야 하기 때문에 Sleep 을 같이 사용했다고 한다. 여기서 발생하는 문제는 Sleep 5초를 주었더니, Timer 까지 5초 동안 멈춘다는 것이다. Timer 라는 것은 기본적으로 시간의 흐름을 측정하는 기능이기 때문에 Sleep 을 했다고 해서 Timer 가 멈추는 일은 생겨서는 안된다. 그러나 실제 샘플을 만들어 보면 ... Timer 가 Sleep 만큼 동작이 멈추는 것을 확인할 수 있다. Windows Forms 는 UI Thread 를 사용하는 것으로 최적화 되어 있으며 여기서 Timer 를 쓰면 UI Thread 에 최적화된 System.Windows.Forms.Timer 가 사용된다. 여기서 문제의 발생이 시작되는 것이다. Sleep 을 사용하게 되면 UI Thread 가 Sleep 이 걸리기 때문에 여기에 속한 Timer 까지도 멈추는 것이다. 이런 문제를 해결하기 위해서는 System.Threading.Timer 를 사용해야 한다. 이 Timer 는 별도의 Thread 에서 동작하기 때문에 Sleep 의 영향을 받지 않는다. 언뜻 보면 쉬운 해결 방법인 것 같지만 Thread 가 분리되었기 때문에 Timer 가 돌아가는 Thread 에서 UI Thread 의 메서드나 컨트롤에 접근하기 위해서는 별도의 명령을 사용해야 하는 문제가 존재한다. 자~ 그럼 여기서 Timer 에 대해서 다시 한번 정리해 보도록 하자. .NET 에서 제공하는 Timer 들 .NET 에서는 기본적으로 3가지 Timer를 제공하고 있다. (MSDN) System.Windows.Forms.Timer - 사용자가 지정한 간격마다 이벤트를 발생시키며 Windows Forms 응용 프로그램에서 사용할 수 있도록 최적화 되어 있다. System...

[Logging] NLog 사용법 정리...

SCSF 에는 기본적으로 Enterprise Library가 사용된다. 예전에도 그랬지만 기능은 훌륭하고 많은 부분에서 최적화(?)된 것일지도 모르지만, 역시나 사용하기에는 뭔가 모르게 무겁고, 사용하지 않는 기능이 더 많다라는 느낌을 지울수가 없다. 이번 프로젝트도 SCSF를 기반으로 하고 있지만, Enterprise Library를 걷어내고 각 부분에 전문화된 오픈 소스를 사용하기로 하였다. 예전에는 Log4Net을 사용했지만, 대량 사용자 환경에서는 메모리 누수와 기타 문제점이 존재한다는 MS 컨설턴트(?)의 전해진 말을 들은 후로는 사용하지 않는다. 대안으로 사용하는 것이 NLog 이다. 조금 후에는 3.0 버전도 나온다고 홈 페이지에 기재되어 있지만, 그 때가 되면 프로젝트는 끝나기 때문에 현재 2.1.0 버전을 사용하기로 했다. [원본 출처] http://cloverink.net/most-useful-nlog-configurations-closed/ 위의 참조 자료에는 다양한 정보들이 존재하므로 꼭 링크를 통해서 관련된 정보를 확인하고 이해하는 것이 좋을 듯 하다. 여기서는 당장 필요한 부분만을 정리하도록 한다. [ Logger 찾기 ] 기본적으로 Logger가 존재하는 클래스를 기반으로 Logger 정보를 구성한다. Logger logger = LogManager.GetCurrentClassLogger(); 주로 Namespace 기반으로 Logger를 설정하는 경우에 유연하게 사용할 수 있다. 또 다른 방법으로는 지정한 문자열로 특정 Logger를 직접 선택하는 방법도 제공된다. 이를 혼용해서 Namespace와 직접 지정 방식을 같이 사용할 수도 있다. 물론 Logger 환경 설정에서 Wildcard (*)를 지정할 수도 있다. Logger logger = LogManager.GetLogger("Database.Connect"); Logger logger = LogManager.Get...