Apollo Studio와 GraphQL을 통합합니다.네트워크 - 섹션 1

GraphQL는 백엔드의 결합 해체와 패턴을 실현하는 데 매우 유용한 도구로 API, 직접 데이터베이스 호출이든 간단한 메모리 저장소든 사용할 수 있다.그러나 도형 조회를 추적할 때 일어난 일이 곧 나타났다.

질문


부가 층을 추가하면 일련의 새로운 문제를 해결해야 한다. 가장 흔히 볼 수 있는 문제는 다음과 같다.

입출력 작업 수


하나의 도형 조회는 간단해 보이지만 여러 API, 읽기/쓰기 데이터 저장소를 호출하는 여러 구역, 심지어 제3자 공급자까지 호출할 수 있다.백엔드가 너무 복잡하거나 백엔드에서 너무 많은 데이터를 추출했기 때문일 수 있다.

총 지속 시간(성과)


일반적으로 이것은 상술한 작업의 부작용일 수 있지만, 입출력 작업은 조회를 아래로 드래그할 수 있다.조건이나 데이터 세트가 올바르면 API 호출이 실행되는 SQL 쿼리의 최적화 수준이 낮아 30초 이상 걸릴 수 있습니다.

잘못


로그 기록은 추적 오류에 매우 유용하지만, 오류가 발생한 조회와 단일 추적의 통계 정보를 보는 데는 더욱 복잡한 작업일 수 있으며, 로그에서 생성해야 한다.

설정 모니터링


Apollo GraphQL은 매우 흔히 볼 수 있는 커뮤니티가 구동하는 GraphQL 구현으로 주로 NodeJ에 사용된다.핵심 라이브러리를 제외하고 그들은 무료(비용 지불 부가 기능 제공)의saaS 플랫폼을 제공하여GraphQL 실현을 감시하는 데 사용한다:Apollo Studio.이러한 솔루션은 다음과 같은 기능을 제공합니다.
  • 아키텍처 변경 내용 추적
  • 당신의 모드를 탐색하고 조회를 실행
  • 아키텍처 사용 보고서
  • 아폴로 스튜디오에 통합


    Apollo 서버GraphQL 구현(NodeJS에 사용) 사용자에게 매우 아름답습니다straight-forward.자바와 파이썬의 실현에는 제3자 공급자도 있지만 이것이 지원의 종점이다.위의 링크는 어떻게 사용하는지create a custom integration도 상세하게 소개했는데 이것이 본고의 중점이다.이 과정은 protobuf 모드를 가져오고 성능 통계 데이터를 Apollo 추적 형식으로 변환하며 메시지에 서명하고 마지막으로 백엔드 프로세스로 변환하여 일괄 처리하는 것을 포함한다.

    Protobuf를 위한 아폴로 스튜디오 과정 생성


    많은 Protobuf의 실현NET 내장 핵, 그러나 나는 protobuf-net를 좋아한다. 왜냐하면 그것은 괜찮고 깨끗하며Apache 2.0의 허가를 받은 실현이기 때문이다.그것 또한 protogen 지원을 받았습니다. 이것은 훌륭한 온라인 생성기입니다. 프로토버프 넷 클래스를 출력해서 사용할 수 있습니다. (CSharp 프로필에 사용됩니다.)링크here에서 최신 모드를 열면 생성기에 붙여넣기만 하면 됩니다.

    주의: 본문을 작성할 때 [(js_preEncoded)=true] 생성기 지원을 받지 않으며 프로토 모드에서 삭제할 수 있습니다.

    아폴로 스튜디오 형식으로 변환


    아폴로에 적합한 형식으로 데이터를 얻기 위해 enable Apollo Tracing enrichmentGraphQL을 사용하여 답변을 볼 수 있습니다.그물다음은 이러한 클래스를 위에서 생성한 클래스로 변환하는 방법에 대한 대형 코드 덤프입니다.
    public class MetricsToTraceConverter
    {
      public Trace? CreateTrace(ExecutionResult result)
      {
        ApolloTrace? trace = result.Extensions != null && result.Extensions.ContainsKey("tracing") ? (ApolloTrace)result.Extensions["tracing"] : null;
    
        var resolvers = trace?.Execution.Resolvers?
          .OrderBy(x => string.Join(":", x.Path), new ConfigurationKeyComparer())
          .ToArray();
    
        var rootTrace = resolvers?.FirstOrDefault(x => x.Path.Count == 1);
        if (rootTrace == null && result.Errors == null)
          return null;
    
        int resolverIndex = 1;
        var rootErrors = result.Errors?.Where(x => x.Path != null && x.Path.Count() == 1).ToArray();
    
        var rootNode = rootTrace != null && resolvers != null
          ? CreateNodes(rootTrace.Path, CreateNodeForResolver(rootTrace, rootErrors), resolvers, ref resolverIndex, GetSubErrors(rootTrace.Path, result.Errors?.ToArray()))
          : new Trace.Node();
    
        if (rootTrace == null && result.Errors != null)
        {
          foreach (var executionError in result.Errors)
            rootNode.Errors.Add(CreateTraceError(executionError));
        }
    
        return new Trace
        {
          StartTime = trace?.StartTime ?? DateTime.Now,
          EndTime = trace?.EndTime ?? DateTime.Now,
          DurationNs = (ulong)(trace?.Duration ?? 0),
          http = new Trace.Http { method = Trace.Http.Method.Post, StatusCode = result.Errors?.Any() == true ? (uint)HttpStatusCode.BadRequest : (uint)HttpStatusCode.OK },
          Root = rootNode
        };
      }
    
      private static Trace.Node CreateNodeForResolver(ApolloTrace.ResolverTrace resolver, ExecutionError[]? executionErrors)
      {
        var node = new Trace.Node
        {
          ResponseName = resolver.FieldName,
          Type = resolver.ReturnType,
          StartTime = (ulong)resolver.StartOffset,
          EndTime = (ulong)(resolver.StartOffset + resolver.Duration),
          ParentType = resolver.ParentType
        };
    
        if (executionErrors != null)
        {
          foreach (var executionError in executionErrors)
            node.Errors.Add(CreateTraceError(executionError));
        }
    
        return node;
      }
    
      private static Trace.Error CreateTraceError(ExecutionError executionError)
      {
        var error = new Trace.Error
        {
          Json = JsonConvert.SerializeObject(executionError),
          Message = executionError.Message
        };
        if (executionError.Locations != null)
          error.Locations.AddRange(executionError.Locations.Select(x => new Trace.Location { Column = (uint)x.Column, Line = (uint)x.Line }));
        return error;
      }
    
      private static ExecutionError[]? GetSubErrors(List<object> path, ExecutionError[]? errors)
      {
        return errors
          ?.Where(x => x.Path != null && x.Path.Count() > path.Count && x.Path.Take(path.Count).SequenceEqual(path))
          .ToArray();
      }
    
      private static Trace.Node CreateNodes(List<object> path, Trace.Node node, ApolloTrace.ResolverTrace[] resolvers,
        ref int resolverIndex, ExecutionError[]? executionErrors)
      {
        bool isArray = node.Type.StartsWith("[") && node.Type.TrimEnd('!').EndsWith("]");
        if (isArray)
        {
          if (resolverIndex < resolvers.Length)
          {
            var resolver = resolvers[resolverIndex];
            while (resolver.Path != null && resolver.Path.Count == path.Count + 2 && resolver.Path.Take(path.Count).SequenceEqual(path))
            {
              var index = (int)(resolver.Path[^2]);
              var subPath = path.Concat(new object[] {index}).ToList();
    
              var previousIndex = resolverIndex;
              node.Childs.Add(CreateNodes(subPath,
                new Trace.Node
                {
                  Index = Convert.ToUInt32(index),
                  ParentType = node.Type,
                  Type = node.Type.TrimStart('[').TrimEnd('!').TrimEnd(']')
                }, resolvers, ref resolverIndex, GetSubErrors(subPath, executionErrors)));
    
              // Avoid infinite loop if the worst happens and we don't match any items for this index (HOW?!?!?)
              if (resolverIndex == previousIndex)
                resolverIndex++;
    
              if (resolverIndex >= resolvers.Length)
                break;
    
              resolver = resolvers[resolverIndex];
            }
          }
        }
        else
        {
          if (resolverIndex < resolvers.Length)
          {
            var resolver = resolvers[resolverIndex];
            while (resolver.Path != null && resolver.Path.Count == path.Count + 1 && resolver.Path.Take(path.Count).SequenceEqual(path))
            {
              var errors = executionErrors?.Where(x => x.Path.SequenceEqual(resolver.Path)).ToArray();
              resolverIndex++;
    
              node.Childs.Add(CreateNodes(resolver.Path, CreateNodeForResolver(resolver, errors), resolvers,
                ref resolverIndex, GetSubErrors(resolver.Path, executionErrors)));
    
              if (resolverIndex >= resolvers.Length)
                break;
    
              resolver = resolvers[resolverIndex];
            }
          }
        }
    
        return node;
      }
    }
    
    그렇다면 이 모든 것은 무엇을 하고 있는 것일까?다음은 개요입니다.
  • '아폴로 추적'을 사용하여 실행 결과에서 풍부한 결과를 추가하는 추적 데이터를 검색합니다.
  • 차원별로 분해기(ConfigurationKeyComparer가 잘했다)를 배열하면 비싼 분해기가 전체 스캐닝을 추적하지 않도록 순서대로 사용할 수 있다.
  • 루트 추적을 찾습니다(첫 번째일 것입니다).
  • 추적에서 모든 루트 오류를 수집합니다.
  • 노드 경로에서 하나의 노드 차원 구조를 구성한다. 이것은 상당히 복잡하지만 데이터 샘플(운행할 때 하나를 보기)을 사용하면 그 작업 원리를 이해하기 쉽다.
  • 추적이 없지만 오류가 있으면 루트에 추가합니다.
  • 추적 대상 (protobuf에서 생성된 클래스) 을 되돌려줍니다. 줄을 서서 대량 발송을 기다리는 데 사용됩니다.
  • 다음


    에서 전체 보고서 클래스를 생성하여 Apollo Studio로 보내는 방법에 대해 알아봅니다.

    좋은 웹페이지 즐겨찾기