영양제 검색 서비스


프리뷰

💎 프로젝트 소개

이번에는 고객이 원하는 영양제의 이름을 검색했을 때, 주어진 Mock Data 안에서 매치되는 영양제 목록을 불러와 화면에 출력해주는 무한스크롤 페이지를 제작했다.


📲 링크

Git. https://github.com/hoonjoo-park/find-my-supplements

배포 주소. https://find-my-supplements.vercel.app/


📩 onChange 이벤트에 따른 데이터 fetch 방식 논의


팀원들과의 논의 결과 두 종류의 fetch 방식이 추려졌고, 이하와 같은 논리적 이유에 따라 2 data fetch 방식을 결정했다.

선택지 두 개

1. state에 모든 API Data를 담아둔 뒤 match 결과를 맵핑한다. ❌

1번

1번과 같은 경우는, 초기 렌더링 시에 한 번만 API요청을 보낸 뒤, state에 모든 데이터를 담아둔다. 그리고 input박스의 onChange 이벤트에 따라 String.match()를 통한 빠른 검색결과 리스팅을 구현할 수 있다.

  • 장점
    • state 배열 내에서 빠르게 match값들을 찾아 리스팅해줄 수 있다.
    • 데이터 정렬 횟수를 최소화 할 수 있다.
  • 단점
    • 만약 API데이터의 크기가 클 경우, 초기 데이터 fetch에 따른 로딩 시간이 굉장히 길어질 수 있다.
    • 메모리를 너무 방대하게 차지하는 공간 비효율성이 초래될 수 있다.

2. onChange마다 해당 값에 맞는 데이터를 DB로부터 get 요청을 보낸다. ✅

2번

2번 방식의 경우, onChange 이벤트가 발생할 때마다, 서버에 Get요청을 보내는 방식이다. 초기에는 MongoDB와 Mongoose를 활용하여 백엔드에서 regex를 활용한 데이터 요청과 반환을 구현해보고자 했으나, 본 프로젝트에서는 mock data를 활용했다.

  • 장점
    • 데이터 fetch 로딩 및 렌더링 시간의 단축을 기대할 수 있다.
    • 메모리 공간을 크게 차지하지 않아도 된다.
  • 단점
    • onChange이벤트는 너무 잦은 이벤트를 야기하기 때문에, 서버에 너무 많은 요청이 일어나 서버에 과부하가 발생할 수 있다.
    • 서버에서 응답 받은 데이터를 매 응답마다 정렬해줘야 한다.

✅ 2번 방식 채택, 그리고 단점 보완에 대한 고찰

이러한 두 방식의 극명한 장단점의 차이로 고민을 하던 중, 1번 방식의 경우, 현재는 데이터의 길이가 600개밖에 안되지만, 추후 확장 가능성을 고려했을 때 효율적이지 못한 방식이 될 것이라 판단하여 2번 방식을 활용하기로 결정했다.

단점 보완

우선, 최대한 API요청 횟수를 줄이기 위해 많은 고민을 했다. 그리고 정렬 기능의 효율성을 높이기 위해 데이터 정렬에 대한 시간복잡도 또한 고려하여 기능 구현을 했다. 이하는 위의 두 원칙을 최대한 실현하기 위해 본 프로젝트에서 활용한 방법들이다.

단점 보완 방식

먼저, 잦은 요청과 그에 따른 데이터 처리량의 부하를 예방하기 위하여, 매 요청마다 모든 일치 데이터를 불러오는 것이 아닌, 상위 20개씩의 데이터만 우선적으로 불러와 이후의 데이터 페칭은 ‘무한스크롤‘방식을 통해 불러와지도록 기능설계를 했다. 또한, 한글 자음이나 모음만 입력됐을 경우에는 API요청이 가지 않도록 사전 방지를 해뒀다.

이후 데이터 간의 우선순위를 매기기 위해 임의로 registCount라는 필드를 DB에 추가했고, 이에 따라 고객이 찾을 확률이 높은 데이터가 먼저 불러와질 수 있도록 유도했다. 즉, 무작위로 데이터가 20개씩 페칭되는 것이 아닌, 고객이 찾고있을 영양제일 확률이 높은, 인기가 가장 많은 영양제를 순서대로 불러온 것이다.

이렇게 우선순위에 따른 데이터 페칭 기능을 구현하기 위해서는 정렬 기능의 구현이 필요했다. 이에 따라 우리 팀은 시간복잡도를 최소화 하기 위해 O(nlogn)의 시간복잡도를 갖는 병합정렬 방식을 채택했다. 사실 현재의 데이터 볼륨 수준에서는 시간복잡도가 크게 유의미하지는 않지만, 추후 데이터가 방대해질 수도 있기에, 정렬에 투입되는 시간비용을 최소화하고자 했다. 또한, 본 프로젝트에서 데이터를 검색하여 불러오는 방식이 onChange마다 get요청이 이루어지는 것이기에, 정렬기능에서도 최대한의 효율을 고려해야 한다고 판단했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { DataTypes } from '@types';

export const mergeSort = (data: DataTypes[]) => {
const merge = (left: DataTypes[], right: DataTypes[]) => {
let mergedArr: DataTypes[] = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i].registCount >= right[j].registCount) {
mergedArr.push(left[i]);
i++;
} else {
mergedArr.push(right[j]);
j++;
}
}
while (i < left.length) {
mergedArr.push(left[i]);
i++;
}
while (j < right.length) {
mergedArr.push(right[j]);
j++;
}
return mergedArr;
};
if (data.length <= 1) return data;
let middle = Math.floor(data.length / 2);
let leftArr: DataTypes[] = mergeSort(data.slice(0, middle)) as DataTypes[];
let rightArr: DataTypes[] = mergeSort(data.slice(middle)) as DataTypes[];
return merge(leftArr, rightArr);
};

요약

문제해결 요약


🧙‍♀️ 문제 해결 과정과 방법

이번 프로젝트에서는 intersectionObserver를 활용한 무한스크롤 기능 구현과, setState의 비동기적 특성 및 dependency에 대한 미숙함에서 문제를 겪었다.

이에 대한 해결 과정 및 학습 정리 내용을 한 페이지에 담기에는 무리가 있다고 판단되어 개별적으로 포스팅 했다.


🍰 요약 및 정리

  1. 잦은 API호출을 방지하기 위해 → 무한스크롤자음과 모음 예외처리를 했다.
  2. 검색과 데이터 호출에 투자되는 시간비용을 줄이기 위해 → 병합정렬 사용과 데이터에 우선순위를 부여했다.

Author

Hoonjoo

Posted on

2022-02-19

Updated on

2022-03-02

Licensed under

Comments