대시보드 중복 데이터 제거 경험

이번 프로젝트에서 페이지네이션 기능을 구현하는 과정 중, 대시보드 데이터에서 중복된 키값이 발생하는 심각한 문제를 발견했습니다. 기본적인 요구 사항은 다음과 같았습니다.

  • 사용자가 로그인하면 /mydashboard 페이지로 이동하고, 해당 사용자가 포함된 대시보드 목록이 화면에 표시됩니다.
  • 대시보드 데이터는 사이드바에도 렌더링되며, 페이지네이션 기능이 적용되어 버튼 클릭 시 페이지가 이동합니다.
  • 데이터를 요청할 때 페이지 번호를 서버에 전달하여 필요한 만큼의 데이터를 받아옵니다.

🚨 문제 배경

데이터를 매핑하던 중 특정 사용자에게서만 대시보드 데이터의 키값이 반복되는 현상이 나타났습니다. 모든 사용자에게 발생하는 문제가 아니었기에 더욱 당황스러웠습니다. 데이터 매핑 시 사용한 키는 서버에서 응답으로 제공되는 대시보드의 고유한 ID 값이었습니다.

✅ 문제 확인

처음에는 대시보드 데이터의 양이 문제라고 생각했습니다. 하지만 전체 데이터를 한 번에 가져오는 것이 아니라, 페이지별로 나누어 받아와 화면에 렌더링하는 방식이었기에 데이터 양이 문제의 원인이라고 단정하기 어려웠습니다. 만약 코드에 문제가 있었다면, 동일한 페이지네이션 기능을 사용하는 다른 데이터(예: 멤버 데이터)에서도 같은 현상이 발생해야 했지만, 그렇지 않았습니다.

정확한 원인을 파악하기 위해 Swagger를 통해 다양한 페이지 번호, 데이터 크기, 그리고 사용자를 변경하며 GET 요청을 보냈고, 응답 데이터를 확인한 결과 키 중복의 이유를 알 수 있었습니다.

데이터를 매핑할 때 응답 데이터에 포함된 대시보드 ID를 키로 사용했는데, 서버에서 응답으로 오는 데이터 자체가 중복되어 있었기 때문에 키값이 중복될 수밖에 없었던 것입니다. 이러한 현상은 서버 API 설계 시 대시보드 멤버를 중복으로 초대할 수 있도록 구현한 데서 비롯된 문제였습니다.

  • 대시보드 멤버를 중복으로 초대할 수 있는 설계로 인해 중복된 대시보드 데이터가 생성될 수 있었습니다.
  • 대시보드 목록 데이터를 응답으로 보낼 때, 같은 ID를 가진 대시보드 데이터가 포함되어 전송되었습니다.
  • 단순히 GET 요청 후 목록을 매핑하는 과정에서 중복된 키값 때문에 데이터 매핑이 올바르게 이루어지지 않았습니다.

프로젝트 기간이 2주로 촉박했고, API 수정은 즉시 이루어질 수 없는 상황이었기에 클라이언트 측에서 이 문제를 해결하기로 결정했습니다.


⭐️ 해결 과정

클라이언트 측에서 이 문제를 해결하려면 불가피하게 네트워크 비용이 발생합니다. API 요청 시 페이지 번호와 데이터 크기를 함께 전송하여 데이터를 받는데, 모든 데이터를 받아와 처리하는 것은 중복이 없는 사용자의 경우 불필요한 작업이며, 중복된 데이터가 있는 경우에도 전체 데이터를 받는 데 시간이 오래 걸립니다. 따라서 중복된 데이터가 있는 경우에만 중복을 제거하고 데이터를 다시 요청하는 방식으로 해결하기로 했습니다.

중복 여부에 따른 데이터 재구성

reduce 메서드를 사용하여 중복된 데이터를 제거한 새로운 대시보드 배열을 생성했습니다. 만약 중복 제거 후 배열의 길이가 6보다 작다면, 중복 데이터가 제거되어 원하는 크기에 미치지 못하는 상황이므로 재귀 함수를 호출하여 부족한 만큼 데이터를 채우도록 했습니다.

const checkDashboard: DashboardDetail[] = dashboards.reduce(
  (acc: DashboardDetail[], cur: DashboardDetail) => {
    if (acc.findIndex(({ id }) => id === cur.id) === -1) {
      acc.push(cur);
    }
    return acc;
  },
  []
);

if (checkDashboard.length < 6) {
  setDashboardList(
    await makeDashboardArr(checkDashboard, page, 6 - checkDashboard.length)
  );
  return;
}

setDashboardList(checkDashboard);

중복이 제거되어 원하는 크기보다 작은 배열, 현재 페이지 번호, 그리고 필요한 데이터 개수를 매개변수로 받아 부족한 데이터를 채워주는 makeDashboardArr 함수를 구현했습니다.

export default async function makeDashboardArr(arr, page, size) {
  async function reFetch() {
    const data = await getDashboard(page + 1);
    const { dashboards } = data;
    return dashboards;
  }

  const fetchArr: DashboardDetail[] = await reFetch();

  const checkDashboard: DashboardDetail[] = fetchArr.reduce(
    (acc: DashboardDetail[], cur: DashboardDetail) => {
      if (acc.findIndex(({ id }) => id === cur.id) === -1) {
        acc.push(cur);
      }
      return acc;
    },
    []
  );
  arr.push(...checkDashboard);

  if (arr.length < 6) {
    makeDashboardArr(arr, page + 1, size - arr.length);
  }

  return arr;
}

🤩 해당 경험을 통해 알게 된 점

코딩 테스트를 준비하며 알고리즘 공부를 할 때, 이러한 지식이 실제 개발에서 어떻게 활용될 수 있을지 의문을 가졌던 적이 있습니다. 하지만 이번 문제를 해결하면서 그 중요성을 깨달았습니다. 특히 테스트를 위해 작성했던 재귀 함수는 당시에는 동작 방식조차 제대로 이해하지 못하는 경우가 많았지만, 실제 상황에서 다양한 조건을 고려하며 직접 구현해보니 큰 성취감을 느꼈습니다.

단순히 백엔드의 문제로 치부하지 않고 클라이언트 측에서 해결 방법을 모색했던 과거의 저에게 칭찬을 보내고 싶습니다. 실제 현업에서는 API 수정이 즉시 불가능한 상황(예: 담당자의 휴가 또는 출장)이 발생할 수 있으며, 이번 문제처럼 사용자에게 중복된 데이터를 노출시켜 서비스 이용에 혼란을 초래할 수 있는 심각한 버그일 수도 있습니다. 비록 네트워크 비용이 추가로 발생하고, API 수정 후에는 더 이상 사용되지 않을 코드일지라도, 당장의 문제를 해결하고 서비스의 안정성을 확보했다는 점, 그리고 제 자신이 한 단계 더 성장했다는 사실에 큰 보람을 느낍니다.