Skip to content

Performance: 위치 검색창 디바운스·AbortController 적용으로 중복 요청 제거 - #298

Merged
arty0928 merged 6 commits into
developfrom
refactor/BADA-388-search-debounce
Oct 12, 2025
Merged

arty0928 merged 6 commits into
developfrom
refactor/BADA-388-search-debounce

Conversation

@arty0928

Copy link
Copy Markdown
Member

#️⃣ 연관된 이슈

close: #297


1) 도입 배경

위치 검색창 입력 시 디바운싱과 쓰로틀링을 중첩 적용하여, 사용자가 서울시 강남구 등 한글을 타이핑할 때마다 최대 22회의 API 호출이 발생했습니다.
React 18의 StrictMode 더블 마운트와 AbortController 부재로 인해 모든 요청이 완료될 때까지 진행되어, 불필요한 네트워크 비용 및 렌더링 과부하가 발생했습니다.

주요 문제점

  1. 디바운스 + 쓰로틀 중첩으로 멈춤마다 1회 + 경계 1회 추가 호출
  2. StrictMode의 마운트/언마운트로 ref 초기화 → 동일 요청 중복 발화
  3. AbortController 미적용으로 이전 요청 미취소 (in-flight 요청 중첩)
  4. INP 111 ms / 전체 타임라인 5995 ms 로 사용자 체감 지연 심화

2) 작업 내용

1️⃣ 디바운스 단일화

  • 쓰로틀 훅(useThrottle) 완전 제거
  • “최종 확정값 1회 호출” 흐름으로 단순화

2️⃣ StrictMode 중복 호출 가드

  • lastReqRef 도입 → (keyword, page, append) 조합으로 요청 키 생성
  • 동일 키 연속 호출 시 조기 return

3️⃣ AbortController 적용

  • controllerRef로 이전 요청 추적
  • 새 요청 시 abort()로 기존 요청 강제 취소
  • signal을 fetch 옵션에 전달해 중간 요청 즉시 중단
  • useEffect clean-up 시점에서도 abort 실행

3) 핵심 코드 스니펫

✅ 1. 디바운스 단일화 (스로틀 제거)

useEffect(() => {
  if (!debouncedKeyword) {
    setSearchResults([]);
    return;
  }
  setPage(1);
  setHasNext(true);
  performSearch(debouncedKeyword, 1, false);
}, [debouncedKeyword, performSearch]);

✅ 2. StrictMode 중복 호출 가드

const lastReqRef = useRef<string | null>(null);

const performSearch = useCallback(async (keyword: string, pageNum = 1, append = false) => {
  const trimmed = keyword.trim();
  const reqKey = `${trimmed}::${pageNum}::${append ? 'append' : 'replace'}`;
  if (lastReqRef.current === reqKey) return;  // 중복 요청 차단
  lastReqRef.current = reqKey;
  // ...
}, []);

✅ 3. AbortController 도입

const controllerRef = useRef<AbortController | null>(null);

const performSearch = useCallback(async (keyword: string, pageNum = 1, append = false) => {
  controllerRef.current?.abort(); // 이전 요청 중단
  const controller = new AbortController();
  controllerRef.current = controller;

  const params = { keyword, page: pageNum, size: 15, signal: controller.signal };
  try {
    const results = await searchPlaces(params);
    // ...
  } catch (err: any) {
    if (err.name !== 'AbortError') console.error('검색 오류:', err);
  }
}, []);

✅ 4. fetch signal 전달

export const searchPlaces = async ({ keyword, page = 1, size = 15, signal }: SearchPlacesParams) => {
  const res = await fetch(
    `https://dapi.kakao.com/v2/local/search/keyword.json?query=${encodeURIComponent(keyword)}&page=${page}&size=${size}`,
    {
      headers: { Authorization: `KakaoAK ${process.env.NEXT_PUBLIC_KAKAO_MAP_REST_API_KEY}` },
      signal, // 요청 취소 신호 연결
    },
  );
  return res.json();
};


성능 최적화 전후 비교

1. API 호출 횟수 개선 및 네트워크 요청 중첩 제거

  • 디바운스 + 쓰로틀 중첩으로 최대 22회 API 호출 발생 | 디바운스 단일화 + AbortController 적용 → 1회로 단일화
  • 이전 요청이 완료될 때까지 모두 유지 (in-flight 22건) | 새 요청 전 abort() 실행으로 즉시 중단
Before After
이전 요청이 완료될 때까지 모두 유지 (in-flight 22건) 새 요청 전 abort() 실행으로 즉시 중단
image image

2. 전체 타임라인 범위 감소 및 INP(입력 응답 시간) 개선

  • 111 ms — 입력 시 지연 발생, 메인 스레드 과부하 | 48 ms — 반응 즉시 렌더링, 부드러운 타이핑
Before After
Full Range: 5995 ms — 연속 JS 실행으로 긴 차트 Full Range: 2506 ms — 불필요한 실행 절반 이하로 감소
image image

5. 요약 수치 비교

지표 Before After 개선율
API 호출 횟수 22회 1회 –95 %
INP (입력 지연) 111 ms 48 ms –57 %
Full Range 5995 ms 2506 ms –58 %
중첩 요청 수 10 건 이상 0 건 –100 %

💡 결과 요약:
불필요한 API 호출과 in-flight 요청을 모두 제거하면서,
사용자의 입력 지연(INP)을 57% 단축하고 렌더링 범위를 절반 이하로 줄였습니다.

@arty0928 arty0928 added this to the 🏷️ 대여 milestone Oct 12, 2025
@arty0928 arty0928 self-assigned this Oct 12, 2025
@arty0928 arty0928 added 🔨 Refactor 코드 리팩토링 👍Performance 성능 최적화 labels Oct 12, 2025
@vercel

vercel Bot commented Oct 12, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
badata Ready Ready Preview Comment Oct 12, 2025 8:12am

@coderabbitai

coderabbitai Bot commented Oct 12, 2025 •

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • 신기능
    • 검색 중 입력 변경 시 이전 요청을 취소하여 최신 결과만 표시됩니다.
  • 버그 수정
    • 동일한 조건의 중복 검색 호출을 방지하여 잘못된/지연된 결과 노출을 줄였습니다.
    • 취소된 요청에서 발생하는 오류를 무시해 불필요한 에러 노출을 줄였습니다.
  • 리팩터링
    • 검색 흐름을 단순화하고 로딩/더보기 상태 표시를 개선했습니다.
    • 검색어가 비어 있을 때 결과를 즉시 초기화합니다.
    • 주소 기록 존재 여부 판단 로직을 안정화했습니다.

Walkthrough

검색 훅에서 스로틀을 제거하고 디바운스 기반 호출로 전환했습니다. AbortController를 도입해 진행 중 요청 취소와 동일 파라미터 요청 중복 방지 로직을 추가했으며, AbortError는 무시하고 그 외 오류만 처리합니다. API 유틸은 AbortSignal을 전달·전파하도록 시그니처를 확장했습니다.

Changes

Cohort / File(s) Summary
검색 훅: 위치 키워드 검색 흐름 리팩터링
src/features/rental/search/hook/useSearchPlacesHooks.ts
스로틀 제거, 디바운스 트리거로 직접 호출. AbortController 도입(요청 취소, 언마운트/키워드 변경 시 abort, signal 전달). 요청 중복 제거(lastReqRef). AbortError 무시, 비-append 시 오류 로깅/노티. 로딩 상태 토글 정리.
검색 훅: 위치 선택/히스토리 계산 로직 정리
src/features/rental/search/hook/useSearchPosHooks.ts
handleSelectPlace 의존성 배열을 비움(재생성 빈도 변화). hasAddressHistory 계산 시 pages 가드 추가 및 직접 파생 계산으로 단순화. 주석 보정.
검색 API 유틸: Abort 지원
src/features/rental/search/utils/address/searchPlaces.ts
SearchPlacesParams에 signal?: AbortSignal 추가. fetch 옵션으로 signal 전달. AbortError 재throw, 기타 오류 로깅 후 재throw.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant UI as SearchInput
  participant HK as useSearchPlacesHooks
  participant AC as AbortController
  participant API as searchPlaces()
  participant BE as fetch/http

  U->>UI: 키워드 입력
  UI->>HK: 디바운스된 키워드 변경
  alt 키워드 비어있음
    HK-->>API: (호출 안 함)
    HK->>HK: 결과 초기화, 진행 중 요청 abort
  else 키워드 있음
    HK->>AC: 기존 요청 abort, 새 컨트롤러 생성
    HK->>HK: 동일 (keyword,page,append) 중복 여부 확인
    alt 중복 요청
      HK-->>API: 스킵
    else 신규 요청
      HK->>API: searchPlaces({ keyword, page, append, signal })
      API->>BE: fetch(..., { signal })
      alt 성공
        BE-->>API: 응답 데이터
        API-->>HK: 결과 반환
        HK->>HK: 상태 업데이트(append 여부 반영)
      else AbortError
        BE--x API: AbortError throw
        API--x HK: AbortError 전파
        HK->>HK: 오류 무시(로딩 정리)
      else 기타 오류
        BE-->>API: 오류
        API--x HK: 오류 전파
        HK->>HK: (append 아님) 로그/노티
      end
    end
  end
  note over HK: 언마운트 시 AC.abort()로 정리
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning useSearchPosHooks.ts 파일의 handleSelectPlace와 hasAddressHistory 관련 변경은 입력 디바운싱 적용 목표와 직접적인 관련이 없어 보여 이 부분은 별도 PR로 분리하거나 변경 사유를 명확히 제시하는 것이 바람직합니다. 관련 없는 훅 로직 수정은 별도 PR로 분리하거나 PR 설명에 해당 변경의 목적과 근거를 명확히 기술해주세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed 변경 사항이 이슈 #297에서 요구한 디바운스 적용 및 API 호출 최소화 목표를 충족하며 중복 호출 방지와 AbortController 적용 구현으로 문제를 해결하고 있습니다.
Description Check ✅ Passed PR 설명은 디바운스 및 AbortController 적용 등 변경된 주요 내용과 배경을 상세히 서술하여 변경 사항과 완전히 연관되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Title Check ✅ Passed 제목은 위치 검색창에 디바운스 및 AbortController 적용으로 중복 요청을 제거하는 주요 성능 개선을 명확하게 요약하여 PR의 핵심 변경 사항을 잘 반영합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/BADA-388-search-debounce

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • BADA-388: Request failed with status code 404

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

Failed to generate code suggestions for PR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/rental/search/hook/useSearchPlacesHooks.ts (1)

53-105: StrictMode에서 검색이 멈추는 중대한 회귀

Line 53~105의 lastReqRef 가드가 동일 키 연속 호출을 전부 막고 있는데, React 18 StrictMode에서는 이펙트가 즉시 정리되고 재실행되면서 첫 번째 호출을 abort()로 끊은 뒤 두 번째 호출이 같은 키라는 이유로 아예 실행되지 않습니다. 개발 모드뿐 아니라 추후 재시도(동일 키 재검색)도 영구적으로 차단됩니다. 요청이 진행 중일 때만 중복을 막도록, 완료/중단 시 lastReqRef를 초기화하는 등 가드를 수정해 주세요.

       try {
         …
       } catch (error) {
         …
       } finally {
+        lastReqRef.current = null;
         setIsLoading(false);
         setIsLoadingMore(false);
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7060b05 and 18e7a06.

📒 Files selected for processing (3)
  • src/features/rental/search/hook/useSearchPlacesHooks.ts (5 hunks)
  • src/features/rental/search/hook/useSearchPosHooks.ts (2 hunks)
  • src/features/rental/search/utils/address/searchPlaces.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/features/rental/search/hook/useSearchPosHooks.ts (2)
src/features/rental/search/utils/address/searchPlaces.ts (1)
  • PlaceSearchResult (2-10)
src/shared/lib/queryClient.ts (1)
  • queryClient (3-10)

Comment thread src/features/rental/search/hook/useSearchPosHooks.ts Outdated
@arty0928
arty0928 changed the base branch from main to develop October 12, 2025 08:13
@arty0928 arty0928 changed the title Refactor/bada 388 search debounce Performance: 위치 검색창 디바운스·AbortController 적용으로 중복 요청 제거 Oct 12, 2025
@arty0928
arty0928 merged commit d681c93 into develop Oct 12, 2025
3 of 4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 4cbea621 Deployed Oct 12, 2025 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👍Performance 성능 최적화 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 대여_검색 디바운싱 적용

1 participant