본문 바로가기
개발일지

Cube.js를 이용한 실시간 데이터 시각화 과정에서 발생한 이슈와 해결

by 2__50 2023. 9. 24.
공부한 내용을 정리한 글입니다 
내용에 오류가 있거나 더 좋은 의견이 있다면 댓글로 남겨주세요.
배움에 큰 도움이 됩니다. 🖋

 

 

Cube.js를 이용한 실시간 데이터 시각화 과정에서 발생한 이슈와 해결


요즘은 계속 대시보드 작업을 하고 있다. 서비스에서 데이터 처리와 시각화에 Cube.js라는 플랫폼을 이용하고 있는데, 최근에 Cube.js를 이용해 실시간으로 데이터를 적재하고 보여주는 과정에서 이슈가 있었기에 문제와 해결 과정을 기록하고자 한다.

 

Cube.js를 사용하면 마치 graphQL처럼 프론트에서 직접 DB에 query를 날려 원하는 데이터를 가져올 수 있다.

 

 

cube.js query문

{
  "limit": 5000,
  "measures": [ // y축
    "default.impressions",
    "default.clicks",
    "default.reach"
  ],
  "dimensions": [ // x축
    "default.createdAt"
  ],
  "timeDimensions": [
    {
      "dimension": "default.createdAt",
      "granularity": "day",
      "dateRange": [
        "2023-03-15",
        "2023-05-31"
      ]
    }
  ],
  "order": {
    "default.impressions": "desc"
  }
}

 

위 배경 지식을 바탕으로 기존 작업 흐름을 잠시 공유하면,
대시보드에 노출되는 데이터는 수시로 바뀔 수가 있기에 query문은 서버에서 받아오고 있었고, 프론트에서 cube.js를 통해 query를 날리면 서버에서 페이스북, 카카오톡 등 사용자가 연동한 채널에서 광고 데이터를 받아와 DB에 적재했다. 그러면 다시 그 DB에 적재된 데이터를 query를 통해 프론트에서 가져와 화면에 보여주는 형식이었다.

 

받아오는 데이터 양이 적지 않기 때문에, 처음에는 각 채널에서 데이터를 실시간으로 받아오고 새로고침을 할 때마다 새로 적재된 만큼 데이터가 업데이트 되는 형식으로 처리를 했다. 사용자의 요청과 동시에 데이터는 계속해서 DB에 적재가 될테고, 새로고침을 할 때마다 점점 더 많은 양의 데이터를 볼 수 있게 되는 것이다.

 

그런데 이번 주 미팅에서, 데이터가 새로고침할 때마다 변하는 형식이 아닌 처음부터 완성된 값을 볼 수 있도록 해야한다는 안이 나왔다. 문제는, 현재 프론트에서 cube.js를 통해 직접 query를 하고 있기 때문에, query를 하는 시점이 적재가 모두 완료된 시점인지 아닌지를 알 수  없다는 것이었다. 이에 서버팀에서 2가지 제안을 주셨다.

 

 

1. 서버에서 cube.js에 직접 쿼리를 날리고, DB에 적재가 완료되면 cube.js에서 응답받은 형식 그대로 프론트에 내려준다.
2. 프론트에서 cube.js를 걷어낸다. 현재 cube.js 내장 메서드를 통해 가공하던 데이터를 서버측에서 가공해 프론트에 내려준다.

 

 

첫번째 :

현재는 데이터를 그래프로 나타낼 때 cube.js에서 제공하는 다양한 내장 메서드를 사용하고 있기 때문에, 이것이 가능하려면 먼저 응답 형식만 같아도 cube.js에서 제공하는 내장 메서드 사용이 가능한지를 확인해야 했다. 그래서 먼저 간단히 JSON.parse(JSON.stringify (obj))로 응답값을 복사해 보았다. 모양이 같지만 전혀 다른 obj로 복사한 것이다.

 

 

JSON.parse(JSON.stringify(obj))로 복사

// 1. Error : rawData() not a function
// 2. query를 통해 만들어진 고유의 obj여야 하는 듯

const copiedResultSet = JSON.parse(JSON.stringify(resultSet))

copiedObj?.rawData()

 

 

결과는 실패.
rawData() not a function 에러가 떴고, query를 통해 만들어진 고유의 obj만이 해당 메서드를 호출할 수 있는 듯 했다.

그렇다면 객체 그대로 복사하면 성공할까?
두번째는 lodash.cloneDeep()을 이용해 복사를 했다.

 

 

lodash.cloneDeep() 이용해 객체 그대로 복사

import _ from 'lodash'

const copiedResultSet = _.cloneDeep(resultSet)

copiedObj?.rawData()

 

 

결과는 성공. cube.js의 내장메서드를 사용할 수 있었다.

 

 

두번째 :

현재는 아래와 같이 cube.js에서 제공하는 내장 메서드를 통해 DB에 쿼리를 날리고 그래프를 그리는 등의 처리가 전부 이루어지고 있다. 따라서 두번째안으로 갈 경우 프론트에서는 cube.js를 사용하는 모든 코드를 걷어내야 했고, 서버에서도 현재 프론트에서 처리하고 있던 차트 데이터 가공을 서버에서 처리해줘야 했기 때문에 양쪽 모두 리소스가 많이 드는 작업이었다. 그러나 다행히 첫번째안이 성공해서 두번째안까지는 고려하지 않아도 됐다.

 

 

cube.js의 QueryBuilder를 통해 query를 날리고, 차트를 렌더하는 모습

<QueryBuilder
      defaultChartType={type as ChartType | undefined}
      query={cubeQuery}
      cubejsApi={cubejsApi}
      render={(renderers) => {
        const { resultSet, loadingState } = renderers;
    

        if (loadingState?.isLoading) {
          return <Loader center />;

 

 

그러나 계속 좀 더 좋은 방법이 있을 것 같다는 생각이 들었고, 이런 아이디어가 떠올라 제안을 드렸다.

1. 화면에 데이터를 보여줄 일이 생기면, 먼저 서버에 데이터가 다 적재됐는지 여부를 확인하는 api를 요청한다.

2. 서버에서 success 응답을 주면, 그 때 cube.js로 query를 보낸다.

 

이렇게 하면 DB에 적재가 완료됐을 때만 데이터를 가져 올 수 있고, 한 페이지에서 여러 그래프를 보여주게 되더라도 적재 완료 여부만 확인하는 비교적 가벼운 통신이기에 성능상 큰 이슈는 없을 것 같았다. 무엇보다 프론트, 서버 둘 다 기존 코드를 크게 변경하지 않고도 문제를 해결할 수 있는 방법이었다.

다행히 서버팀에서 괜찮을 것 같다는 피드백을 주셨고, 제한된 일정 안에서 비교적 적은 리소스만으로 문제를 해결할 수 있었다.

댓글