Skip to content

♻️ refactor: 중복된 쿼리 키를 통합해 같은 요청이 여러 번 나가던 것 제거 - #12

Open
za0012 wants to merge 2 commits into
Nextsfrom
refactor/unify-query-keys
Open

za0012 wants to merge 2 commits into
Nextsfrom
refactor/unify-query-keys

Conversation

@za0012

@za0012 za0012 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

같은 데이터를 화면마다 다른 queryKey로 조회하고 있어, 한 페이지 로드에 동일한 요청이 여러 번 나가고 있었습니다.

TanStack Query의 캐시는 queryKey로만 조회합니다. queryFn이 같은 함수인지는 보지 않습니다. 키 이름이 갈린 만큼 같은 응답이 별개 엔트리로 저장되고, 요청도 각각 나갑니다.

무엇이 갈려 있었나

1) getMyMemberInfo — 같은 study_members 행이 키 4개

위치
["userInfo"] mypage/page.tsx, Sidebar.tsx
["myInfo"] MySolves/view.tsx, problems/page.tsx
["myInfoInAccountSetting"] AccountSetting/view.tsx
["getMyMemberInfo"] ProfileSetting/view.tsx, StudySetting/view.tsx

구현도 api/members.tsapi/study.ts에 두 벌 있었습니다.

2) 히트맵 / 월별 차트 — 완전히 같은 SQL이 키 2개

Heatmap/service.tsLineChart/service.ts가 둘 다 problems.select("date").eq("member_id", …)를 돌리고 뒤에서 집계만 다르게 하고 있었습니다.

어떻게 고쳤나

src/lib/query/에 키와 훅을 한 번만 정의하고 호출부를 교체했습니다.

  • useMyMemberInfo()MY_MEMBER_INFO_KEY 하나
  • useProblemDates(memberId, select) — 원본은 한 번 받고 화면별 집계는 select로 분기

컴포넌트 위치는 그대로 뒀습니다. 캐시가 전역이라 키만 맞으면 공유되므로, 상위로 끌어올려 props로 내릴 이유가 없습니다.

측정 (로그인 상태, 개발 서버)

performance.getEntriesByType('resource')로 Supabase 요청만 집계했습니다.

화면
/settings 10건 (study_members?select=* 3회) 6건 (1회)
/mypage 12건 (study_members?select=* 2회, problems?select=date 2회) 9건 (중복 0건)

/mypage는 이제 모든 엔드포인트가 정확히 1회입니다.

StrictMode 이중 렌더가 아닙니다. 그거라면 전부 2회여야 하는데, 키가 갈리지 않은 studies?select=*&id=eq.9는 처음부터 1회였습니다.

곁다리로 같이 고쳐진 것

  • ProfileSettinginvalidateQueries 3줄 → 1줄. 사본이 넷이라 손으로 나열하다가 ["myInfo"]["myInfoInAccountSetting"] 두 개가 빠져 있었습니다. 그 둘은 .id/.study_id만 읽고 있어 화면에 드러나는 문제는 없었지만, 이름을 읽는 컴포넌트가 하나라도 붙으면 그때 터집니다.
  • problems/page.tsx["memberList"]studyId 추가 + enabled 게이트. studyId 없이 첫 렌더에서 실행돼 에러 상태로 굳고, 키가 고정이라 재조회도 되지 않던 자리였습니다.
  • getMyMemberInfo 중복 구현 제거. 스터디 미참여 시 throw하던 api/members.ts 쪽을 지우고 null을 돌려주는 api/study.ts 쪽을 남겼습니다. 참여/생성 화면 전환이 null에 걸려 있습니다.
  • countByMonth는 문자열을 자릅니다. 기존 구현은 new Date(date).toISOString().slice(0,7)로 Date를 한 번 거쳤는데, 'YYYY-MM-DD'에서 앞 7자를 자르면 되는 일이라 되돌릴 이유가 없었습니다.

검증

tsc --noEmit   0
bun test       18 pass (12 → 18, countByDate·countByMonth 테스트 추가)
bun run build  통과
biome          변경 파일 0 error

createAndFillHeatmapcreateSixMonthObject의 시그니처는 건드리지 않아 기존 타임존 경계값 테스트가 그대로 유지됩니다. 집계 단계를 순수 함수로 새로 빼고 거기에 테스트를 붙였습니다.

화면 확인 (/mypage): 월별 축 3~8월 6개, 난이도 막대 3개, 히트맵 합계 8건, 말일(2026-05-31) 셀 보존.

남은 것 (이 PR 범위 밖)

PlatformChart/service.tsgetLineGhDataMonth 세 번째 사본이 있습니다. PieChartWithCustomizedLabelmypage/page.tsx에서 주석 처리돼 있어 지금은 실행되지 않아 건드리지 않았습니다.

🤖 Generated with Claude Code

za0012 and others added 2 commits August 11, 2026 11:01
같은 study_members 행을 화면마다 다른 queryKey로 조회하고 있었다.
TanStack Query는 queryKey로만 캐시를 찾고 queryFn이 같은지는 보지 않으므로,
이름이 갈린 만큼 같은 응답이 별개 엔트리로 저장되고 요청도 각각 나갔다.

  ["userInfo"]               mypage/page, Sidebar
  ["myInfo"]                 MySolves, problems/page
  ["myInfoInAccountSetting"] AccountSetting
  ["getMyMemberInfo"]        ProfileSetting, StudySetting

lib/query/useMyMemberInfo.ts 에 키와 훅을 한 번만 정의하고 전 호출부를 교체했다.
컴포넌트 위치는 그대로 두었다. 캐시가 전역이라 키만 맞으면 공유되므로
상위로 끌어올려 props로 내릴 이유가 없다.

- getMyMemberInfo 중복 구현 제거(api/members.ts). 스터디 미참여 시 throw 대신
  null을 돌려주는 api/study.ts 쪽을 남겼다. 참여/생성 화면 전환이 null에 걸려 있다.
- ProfileSetting의 invalidateQueries 3줄 → 1줄. 키가 하나라 구독자가 함께 갱신된다.
- problems/page의 ["memberList"]에 studyId를 넣고 enabled를 걸었다.
  studyId 없이 실행되면 에러 상태로 굳고, 키가 고정이라 재조회도 되지 않았다.

측정(로그인 상태, 개발 서버):
  /settings  요청 10건 → 6건 (study_members?select=* 3회 → 1회)
  /mypage    요청 12건 → 10건 (study_members?select=* 2회 → 1회)

검증: tsc --noEmit 0 / bun test 12 pass / biome 변경 파일 0 error

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Heatmap/service.ts 와 LineChart/service.ts 가 완전히 같은 쿼리를 돌리고 있었다.

  supabase.from("problems").select("date").eq("member_id", memberId)

키가 ["heatmap"] / ["lineGraph"]로 갈려 있어 캐시가 공유되지 않고
마이페이지 한 번 로드에 같은 SQL이 두 번 나갔다.

lib/query/useProblemDates.ts 로 키를 합치고, 화면별 집계는 select로 갈랐다.
select는 화면마다 다르므로 인자로 받되 호출부에서 useCallback으로 감쌌다.
(기존 인라인 화살표는 렌더마다 정체성이 바뀌어 집계가 매번 다시 돌았다)

- countByDate / countByMonth 를 각 hook.ts에 순수 함수로 추가하고 테스트를 붙였다.
  기존 createAndFillHeatmap / createSixMonthObject 시그니처는 건드리지 않아
  타임존 경계값 테스트가 그대로 유지된다.
- countByMonth는 'YYYY-MM-DD' 문자열 앞 7자를 자른다. 기존 구현은
  new Date(date).toISOString().slice(0,7)로 Date를 거쳤는데, 되돌릴 이유가 없다.
- 중복 service 2개 삭제.

측정(로그인 상태, 개발 서버):
  /mypage  요청 12건 → 9건. 중복 엔드포인트 0건(모든 요청이 1회)
  화면 확인: 월별 축 3~8월 6개, 난이도 막대 3개, 히트맵 합계 8건,
            말일(2026-05-31) 셀 보존

검증: tsc --noEmit 0 / bun test 18 pass

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@za0012
za0012 deployed to ai-review August 11, 2026 02:08 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

자동 PR 코드 리뷰

  • 실행 유형: PR 리뷰
  • 생성 시각: 2026-08-11T02:09:09.997Z
  • 기준 커밋: 637a1035ed12e43cfa4b2dd79a8468e05a4ec65e
  • 대상 커밋: 210dc1db52443e616b392b4ccb2006e65c9b8083
  • 변경 파일 수: 19
  • 발견 항목 수: 0
  • 이번 PR 영향 가능 항목: 0
  • 기존 실패 추정 항목: 0

검증 결과

이번 PR 영향 가능 항목

이번 PR 변경 파일에서 직접 발생한 것으로 보이는 항목은 없습니다.

기존 실패로 추정되는 항목

기존 실패로 분류된 항목은 없습니다.

경고

  • OpenAI review skipped because OPENAI_API_KEY is missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant