현재 개발하고 있는 서비스는 사용자가 입력한 키워드의 검색량을 조회해주는 기능을 제공하고 있다. 최대 100개까지 한 번에 검색할 수 있는데, 이 과정이 너무 오래 걸린다는 사용자 피드백이 지속적으로 들어왔다. 실제로 100개의 키워드를 검색하면 약 7분이라는 긴 시간이 소요되었다.
문제를 분석해보니 다음과 같은 원인이 있었다:
이러한 문제를 해결하기 위해 코드를 리팩토링하기로 결정했다.
가장 먼저 키워드를 개별적으로 처리하는 대신 '배치(batch)'로 묶어서 처리하는 방식을 도입했다. 배치 처리란 여러 작업을 그룹으로 묶어 한 번에 처리하는 방법이다.
// 기존 방식: 키워드 하나씩 처리
const processKeyword = async (keyword) => {
const result = await fetchKeywordData(keyword);
// 결과 처리
};
// 개선된 방식: 여러 키워드를 배치로 처리
const processBatch = async (keywords) => {
const results = await fetchKeywordDataBatch(keywords);
// 여러 결과 한 번에 처리
};
이를 위해 새로운 API 엔드포인트를 만들었다:
/api/keywords/batch
: 여러 키워드를 한 번에 처리/api/naver-datalab/batch
: 여러 데이터랩 요청을 동시에 처리이 API들은 기존의 단일 키워드 처리 API를 활용하되, 여러 키워드를 한 번에 요청할 수 있도록 수정한 것이다.
다음으로 Promise.all을 활용해 여러 API 요청을 병렬로 처리하도록 구현했다. 병렬 처리는 여러 작업을 동시에 실행하여 총 소요 시간을 줄이는 방법이다.
// 기존 방식: 순차 처리
for (const keyword of keywords) {
await processKeyword(keyword);
}
// 개선된 방식: 병렬 처리
const batchSize = 5; // 한 번에 처리할 키워드 수
for (let i = 0; i < keywords.length; i += batchSize) {
const batch = keywords.slice(i, i + batchSize);
await Promise.all(batch.map(keyword => processKeyword(keyword)));
}
특히 네이버 API의 특성을 고려하여 최적의 배치 크기를 5개로 설정했다. 이는 API 서버의 부하와 클라이언트 처리 능력 사이의 균형점이다.
기존에는 각 키워드 결과가 도착할 때마다 상태를 업데이트했는데, 이 방식은 불필요한 렌더링을 많이 발생시킨다. 개선된 코드에서는 배치 단위로 상태를 업데이트하도록 변경했다.
// 기존 방식: 개별 결과 추가
const addKeywordResult = (result) =>
set((state) => ({
keywordResults: [...state.keywordResults, result],
}));
// 개선된 방식: 여러 결과 한 번에 추가
const addKeywordResults = (results) =>
set((state) => ({
keywordResults: [...state.keywordResults, ...results],
}));
이렇게 함으로써 상태 업데이트 횟수를 크게 줄일 수 있었다.
동일한 키워드에 대한 중복 요청을 방지하기 위해 간단한 메모리 캐싱 시스템을 구현했다. 이미 검색한 키워드는 캐시에서 바로 결과를 가져올 수 있도록 했다.
// 캐싱 상태 추가
cachedResults: new Map(),
// 결과 캐싱 로직
addKeywordResult: (result) =>
set((state) => ({
keywordResults: [...state.keywordResults, result],
cachedResults: new Map(state.cachedResults).set(result.keyword, result)
})),
이는 사용자가 같은 키워드를 반복해서 검색할 때 성능을 크게 향상시킨다.
리팩토링 후 성능 테스트를 진행한 결과, 100개 키워드 검색 시간이 7분에서 약 1분 이내로 단축되었다. 이는 약 85% 이상의 성능 향상을 의미한다.
특히 다음과 같은 측면에서 개선이 이루어졌다:
네이버 API는 초당 요청 수 제한이 있어서 너무 많은 병렬 요청을 보내면 오류가 발생할 수 있다. 이 문제를 해결하기 위해 적절한 배치 크기(5개)를 선택하고, 배치 간에 약간의 지연을 추가했다.
// 다음 배치 처리 전 잠시 대기
setTimeout(processBatch, 10);
배치 크기가 커질수록 메모리 사용량도 증가한다. 이 트레이드오프를 관리하기 위해 최적의 배치 크기를 찾는 테스트를 여러 번 진행했다.
배치 처리 중 일부 요청이 실패할 경우에도 전체 검색이 중단되지 않도록 오류 처리 로직을 강화했다.
try {
// API 요청 처리
} catch (error) {
// 실패한 요청에 대한 결과 생성
batchResultsRef.current.set(keyword, {
keyword,
keywordData: null,
pcData: null,
mobileData: null
});
}
이번 리팩토링을 통해 배우게 된 핵심 포인트는 다음과 같다:
이런 최적화는 단순히 코드 개선에 그치지 않고 사용자 경험을 크게 향상시킨다. 검색 시간이 짧아지면서 사용자는 더 많은 키워드를 더 빠르게 분석할 수 있게 되었다.
앞으로도 서비스 성능을 지속적으로 모니터링하고 개선해 나갈 계획이다. 다음 단계로는 서버 사이드 캐싱이나 웹 워커를 활용한 추가 최적화를 고려하고 있다.