본문 바로가기

APM

데이터독 GraphQL 연동

- GraphQL 이란?

GraphQL은 Facebook에서 만든 데이터 쿼리 스펙(2012년 개발 / 2015년 공개).

SQL과 같은 쿼리 스펙으로 해당 쿼리를 이해할 수 있는 MySQL과 같은 서버가 필요한데, 보통 ApolloServer를 사용함(HTTP POST 메소드 사용). 리턴은 JSON 형식이 기본.

 

- 왜 GraphQL을 만들었을까?

Over-fetching, Under-fetching 문제를 해결하기 위해 탄생.

Over-fetching: 쇼핑몰 메인화면에서 상품 정보는 상품 이름, 가격, 이미지 링크 정보만 필요하지만 RestAPI에서 상품의 모든 정보를 가져오고, 프론트 화면에서 필요한 정보만 보여줄 경우.

Under-fetching: 장바구니 화면에서 사용자 정보와 상품 정보를 보여주는데, 해당 화면을 보여주기 위해서는 RestAPI를 여러번 사용할 경우.

Over-fetching의 경우, 필요없는 정보를 받아와 네트워크 자원 낭비.

Under-fetching의 경우, 여러번 RestAPI를 호출하여 프론트의 레이턴시 증가. 심할 경우 프론트 팀은 백앤드 팀에 해당 쿼리에 대한 요청 필요.

특히 멀티디바이스 환경으로 오면서 해당 문제는 더 크게 발생.

따라서 아래와 같이 REST API 호출 전, GraphQL 서버(ApplloServer)를 통해 Over-fetching을 피해 필요한 정보만 가져오고, Under-fetching을 피해 백앤드 팀에 의존하지 않고 기존 Rest API를 묶어서 하나의 GraphQL 쿼리를 사용할 수 있음.

https://www.apollographql.com/blog/graphql/basics/graphql-vs-rest/

 

 

- 꼭 GraphQL 쿼리를 이해할 수 있는 ApolloServer를 RestAPI 뒷단에서만 사용할 수 있을까?

아니다. ApolloServer은 GraphQL 쿼리를 이해할 수 있는 Java의 톰캣, Node의 express와 같은 서버로 개발이 필요하기 때문에 뒷단에 RestAPI, RDBMS, NoSQL 등 다양한 서비스들 사용 가능.

https://www.apollographql.com/docs/apollo-server/

 

 

- SQL과 GraphQL 쿼리 비교

SQL

## SQL
# Select
SELECT id, text FROM TodoList

# Where
SELECT id, text FROM TodoList WHERE id = 1

# Insert
INSERT INTO TodoList(id, text) VALUES(1, "열시미살자") 

# Update
UPDATE TodoList SET text = "그냥살자" WHERE id = 1

# Delete
DELETE FROM WHERE id = 1

GraphQL

## GraphQL
# Select
Query {
  getAllTodoList {
    id
    text
  }
}

# Where 
Query {
  getTodoList(id: 1) {
    id
    text
  }
}

# Insert
mutation($text: String!, $userId: ID!){
  postTodo(text: "열시미살자", userId: 1) {
    id
    text
  }
}

# Update
mutation($text: String!, $userId: ID!){
  updateTodo(text: "그냥 살자", userId: 1){
    id
    text
  }
}

# Delete
mutation($text: String!, $deletetodoId: ID!){
  deleteTodo(id: 1)
}

 

GraphQL은 데이터 포맷에 대한 스펙일 뿐, 결국에는 아래와 같이 코드에서 리턴 값을 보내줘야 함.

  ## SELECT는 보통 Query으로 정의한 프로퍼티 내에서 개발
  Query: {
    getAllTodoList() {
      return TodoList;
    },
    getTodoList(root, { id }) {
      return TodoList.find((todoList) => todoList.id === id);
    },
  },
  
  ## INSERT, UPDATE, DELETE는 보통 Mutation으로 정의한 프로퍼티 내에서 개발
  Mutation: {
    postTodo(_, { text, userId }) {
      const newtodo = {
        id: TodoList.length + 1,
        text,
        userId,
      };
      TodoList.push(newtodo);
      return newtodo;
    },
    deleteTodo(_, { id }) {
      const todo = TodoList.find((todo) => todo.id === id);
      if (!todo) return false;
      Todo = TodoList.filter((todo) => todo.id !== id);
      return true;
    },
  },

 

 

 

- 기타 GraphQL 정보

기본 통신 포트는 4000번이며, 보통 Node.js와 많이 사용. 그러나 대부분 언어에서 ApolloServer를 지원함.

 

 

- GraphQL을 Datadog APM과 연동

Node.js에서 APM만 구성되었으면 특별히 구성할 건 없음.

이미 아래와 같이 Graphql을 지원하고 있음. (엄밀히 따지면 ApolloServer를 지원)

https://docs.datadoghq.com/tracing/setup_overview/compatibility_requirements/nodejs/

 

 

- 그럼 APM을 연동했지만 왜 GraphQL Trace를 볼 수 없을까?

DD 콘솔에서 Traces 항목을 보면 아래와 같이 오직 POST로만 호출하고 있는 것이 보임.

 

Frame GraphQL를 확인해봐도 역시나 GraphQL 쿼리 내역을 확인할 수 없음.

 

그 이유는 GraphQL을 이해하는 ApolloServer는 Node.js의 express 모듈에 디펜던시가 있기 때문.

즉, ApolloServer는 GraphQL 쿼리를 이해하는 것일 뿐 HTTP 통신은 express에서 담당.

Query {
  getAllTodoList {
    id
    text
  }
}

 

결국 Front 사이드(ApolloClient)에서 날린 GraphQL 쿼리는 아래와 같이 HTTP POST 메소드를 보내고,

express 서버에서는 HTTP POST 메소드를 받아서 ApolloServer에게 전달하고,

아래 HTTP POST Body의 GraphQL 형식의 Json을 이해하는 ApolloServer는 전달받은 쿼리에 대한 응답을 할 수 있게 됨.

 

즉 GraphQL 쿼리의 인생사는 아래 그림과 같음.

GraphQL 쿼리의 인생

 

 

- 그래서 어떻게, 어디서 GraphQL 쿼리 리소스를 확인할 수 있을까?

DD 콘솔 → APM → Services → ADDITIONAL OPERATIONS에서 확인할 수 있음.

GraphQL Resources

 

기존에는 Trace 항목에서 확인이 가능했던 거 같음.

참고: https://github.com/DataDog/dd-trace-js/pull/1445

요약하면 기존에는 가능했지만, 2021년 6월부터 APM Services에서 ADDITIONAL 항목에서만 확인 가능.

왜 바꿨냐하면 express 모듈 아래에 ApolloServer가 동작하기 때문에, Primary 모듈인 express 아래에 graphql을 넣었음.

 

 

- 그럼 이전처럼 Trace에서 볼 수는 없는가?

https://datadoghq.dev/dd-trace-js/interfaces/plugins.graphql.html#service

dd-trace에서 graphql 모듈을 별로 서비스 이름으로 수정하면 Trace 항목에서도 확인 가능.

import tracer from "dd-trace";

tracer.init({
  env: "todo-stg",
  service: "todo-graphql-stg",
  logInjection: true,
  enabled: true,
});

tracer.use("graphql", {
  source: true,
  service: "todo-graphql-trace-stg",
});

console.log("Starting dd tracing...");

export default tracer;

 

참고: https://tech.kakao.com/2019/08/01/graphql-basic/, https://www.apollographql.com/docs/