우리는 TanStack Query에서 캐시 무효화를 계속 깨뜨렸고, 그래서 수동으로 관리하는 것을 중단했습니다.
Source: Dev.to
실제 고통 포인트 (프로덕션에서)
- 수동으로 만든 캐시 키는 오류가 발생하기 쉬움
- 무효화 로직을 이해하기 어려움
- 생성된 훅은 캐시 일관성을 해결하지 못함
- 팀 전체에 공유되는 표준이 없음
이러한 문제들은 몇 주 뒤에 오래된 UI 버그 형태로 나타나는 경우가 많습니다.
아이디어: 캐시 키를 손으로 만들지 않기
모든 곳에서 캐시 키를 정의하는 대신, 우리는 다음을 원했습니다:
- 캐시 키에 대한 단일 진실 소스
- 예측 가능한 무효화 동작
- 생성된 훅과의 원활한 통합
규칙: 캐시 키와 무효화는 직접 작성하지 말고 자동으로 생성해야 합니다.
파이프라인 (문서화된 대로)
Query Cache Flow는 많은 팀이 이미 사용하고 있는 파이프라인을 형식화합니다:
- REST API – 캐시 동작이 사후 생각이 아니라 계약의 일부가 됩니다.
- 핵심 원시 함수:
createQueryGroupCRUD– 모든 것이 쿼리 그룹에서 시작됩니다.
핵심 원시 함수: createQueryGroupCRUD
import { createQueryGroupCRUD } from '@/queries'
export const accountsQueryGroup = createQueryGroupCRUD('accounts')
이 한 줄은 다음을 정의합니다:
- accounts와 관련된 모든 쿼리 키
- CRUD 작업이 관련 쿼리를 어떻게 무효화하는지
- 리스트와 상세 정보에 대한 일관된 구조
문자열 리터럴도, 중복도 없습니다.
생성된 훅과 함께 사용하기
안정적인 쿼리 키를 사용해 생성된 훅을 한 번만 래핑합니다:
export const useAccounts = () =>
generatedUseAccounts({
query: {
queryKey: [accountsQueryGroup.list.queryKey],
},
})
- 쿼리 키는 쿼리 그룹에서 가져옵니다.
- 모든 사용자는 동일한 키 구조를 사용합니다.
- 어떤 컴포넌트도 자체 키를 만들지 않습니다.
뮤테이션과 자동 무효화
뮤테이션 후:
await createAccount.mutateAsync(data)
그룹에 미리 정의된 키를 사용해 쿼리를 무효화합니다:
invalidateQueriesForKeys([
accountsQueryGroup.create.invalidates,
])
무효화 키는 이미 어떤 리스트와 연관된 쿼리를 새로 고쳐야 하는지 알고 있어, 로직을 반복하지 않아도 자동으로 연쇄 무효화가 이루어집니다.
즉흥적인 무효화보다 왜 더 좋은가
전형적인 즉흥 접근 방식:
queryClient.invalidateQueries({ queryKey: ['accounts'] })
queryClient.invalidateQueries({ queryKey: ['accounts', id] })
문제점:
- 키가 곳곳에 중복돼 있습니다.
- 키를 놓치기 쉽습니다.
- 검토하거나 리팩터링하기 어렵습니다.
쿼리 그룹을 사용하면:
- 무효화 의도가 명확히 드러납니다.
- 동작이 중앙 집중화됩니다.
- 리뷰가 간단해집니다.
이 패턴이 제공하는 것
- 일관된 캐시 키 구조
- 자동 연쇄 무효화
- 타입 안전한 캐시 작업
- 코드 생성 훅과의 일급 통합
가장 중요한 점: 캐시 키에 대해 전혀 고민하지 않게 됩니다.
언제 이 패턴이 적합한가
다음 경우에 이상적입니다:
- OpenAPI + 코드 생성 사용 프로젝트
- 리스트/상세 관계
- 동일 데이터를 영향을 주는 여러 뮤테이션
- 장기적인 유지 보수성
앱이 아주 작다면 오버헤드가 정당화되지 않을 수 있지만, 규모가 커질수록 이 패턴이 큰 가치를 제공합니다.
이것은 패턴이지 마법이 아니다
Query Cache Flow는 다음과 같은 일을 하지 않습니다:
- TanStack Query에 대한 이해를 대체
- 무효화 방식 은폐
- 설계가 부실한 API 수정
캐시 동작을 형식화함으로써 암묵적인 지식이 되지 않게 합니다.
최종 생각
캐시 무효화는 유명하게 어려운 문제이지만, 대부분의 고통은 구조가 없어서 생기는 것이지 React Query 자체 때문은 아닙니다. 캐시 키와 무효화를 일급 생성 아티팩트로 다루면 문제 자체가 지루해지고—바로 그 지루함이 바로 원하는 결과입니다.
Docs: