[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -2,134 +2,178 @@
|
||||
id: wiki-2026-0508-비동기-데이터-관리
|
||||
title: 비동기 데이터 관리
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: []
|
||||
aliases: [Async Data Management, Server State, Data Fetching]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.92
|
||||
tags: [uncategorized]
|
||||
verification_status: applied
|
||||
tags: [frontend, async, react-query, swr, server-state]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-08
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: typescript
|
||||
framework: react-query
|
||||
---
|
||||
|
||||
# [[비동기 데이터 관리|비동기 데이터 관리]]
|
||||
# 비동기 데이터 관리
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
비동기 데이터 관리(서버 상태 관리)는 API에서 가져온 데이터를 클라이언트 측 애플리케이션 상태와 명확히 분리하여 처리하는 것을 의미합니다 [1]. 이는 네트워크 요청에 따른 데이터 캐싱, 동기화, 로딩 및 에러 사이클 관리를 포함하며, 현대 프론트엔드 시스템 아키텍처의 핵심 요소입니다 [1, 2]. 대규모 앱에서는 RTK Query나 TanStack Query(React Query)와 같은 특화된 도구를 사용하여 비동기 보일러플레이트를 줄이고 효율적인 캐시 관리를 수행합니다 [1, 3, 4].
|
||||
## 매 한 줄
|
||||
> **"매 server state 의 client cache 의 separation"**. 매 fetch / cache / sync / invalidate / retry / dedupe 의 7 concerns 의 library 의 delegation — 매 2026 의 TanStack Query (v5) / SWR / RTK Query 의 standard. Custom `useEffect + fetch` 의 anti-pattern.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
* **서버 상태와 클라이언트 상태의 분리:**
|
||||
최근 프론트엔드 아키텍처에서 가장 중요한 변화 중 하나는 "서버 상태(Server State)"를 "애플리케이션 상태"와 분리하는 것입니다. API에서 가져오는 데이터는 클라이언트 데이터와 근본적으로 다르며, 캐싱, 동기화, 로딩 및 에러 처리가 반드시 필요합니다 [1]. Zustand와 같이 유연한 상태 관리 라이브러리로 비동기(Async) 작업을 직접 다루게 되면, 팀원마다 콜백, 프로미스, 미들웨어 등 서로 다른 패턴을 사용하여 일관성이 떨어지고 유지보수가 어려워지는 한계가 발생할 수 있습니다 [3, 5].
|
||||
## 매 핵심
|
||||
|
||||
* **비동기 데이터 관리 최적화 도구:**
|
||||
이러한 문제를 해결하기 위해 TanStack Query(React Query)와 RTK Query 같은 라이브러리가 사실상의 표준으로 자리 잡았습니다 [1].
|
||||
* **TanStack Query:** 강력한 캐싱 레이어를 제공하여 불필요한 네트워크 중복 요청을 줄이고 데이터의 최신 상태를 유지합니다. 무한 스크롤(infinite scrolling)이나 낙관적 업데이트(optimistic updates)와 같은 복잡한 비동기 기능을 단순하게 구현할 수 있습니다 [2].
|
||||
* **RTK Query:** Redux 생태계에서 비동기 작업이 많은 앱을 위해 캐싱, 중복 제거, 자동 데이터 재요청(refetching), 캐시 무효화 기능을 기본으로 제공하여 비동기 보일러플레이트 코드를 사실상 제거합니다 [3, 4].
|
||||
### 매 server vs client state
|
||||
- **client state**: form input, modal open/close, theme — local, ephemeral. Zustand/useState.
|
||||
- **server state**: 매 remote source of truth — async, stale, shared, cached. TanStack Query.
|
||||
|
||||
* **구조적 분리와 아키텍처:**
|
||||
API 계층은 일반적으로 독립적인 경계로 구성되며, 요청 선언부와 커스텀 훅(Custom Hooks)은 특정 기능(feature) 폴더 내에 함께 배치(colocate)됩니다. 이를 통해 네트워크 관련 비동기 로직을 UI 컴포넌트와 완벽히 디커플링(decoupling)하여 유지보수성을 향상시킵니다 [2].
|
||||
### 매 7 concerns
|
||||
1. **Fetch**: HTTP request + abort.
|
||||
2. **Cache**: key-based store.
|
||||
3. **Dedupe**: 매 simultaneous request 의 share.
|
||||
4. **Stale**: time-based freshness.
|
||||
5. **Background refetch**: window focus, reconnect.
|
||||
6. **Retry**: exponential backoff.
|
||||
7. **Invalidate**: mutation 후 refetch.
|
||||
|
||||
* **성능 최적화 및 안정성:**
|
||||
* **디바운싱과 쓰로틀링:** 사용자 입력(예: 검색)에 의해 트리거되는 값비싼 비동기 API 호출은 디바운싱(debouncing)이나 쓰로틀링(throttling)을 통해 횟수를 제한해야 합니다. 이는 과도한 API 호출을 방지하여 클라이언트 성능을 향상시키고 서버 부하를 줄여줍니다 [6, 7].
|
||||
* **메모리 누수 방지:** 이벤트 리스너나 구독(subscriptions) 등 정리가 필요한 비동기 사이드 이펙트의 경우, 컴포넌트가 언마운트될 때 리소스를 해제하지 않으면 메모리 누수(memory leaks)가 발생할 수 있습니다. 이를 막기 위해 반드시 `useEffect`에서 클린업(cleanup) 함수를 반환해야 합니다 [8, 9].
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
### Related Concepts
|
||||
- TanStack Query 및 RTK Query
|
||||
- 연결 이유: 서버 상태(API 데이터) 관리에 있어 캐싱, 중복 요청 제거, 자동 재요청 등을 처리하기 위한 현대적인 필수 표준 도구로 소스에서 강조되고 있기 때문입니다 [1, 2, 4].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 비동기 데이터 관리에서 발생하는 보일러플레이트 감소 원리와 데이터 동기화 메커니즘.
|
||||
|
||||
- 서버 상태 (Server State)
|
||||
- 연결 이유: API로부터 패치(fetch)되는 데이터는 클라이언트 상태와 성격이 완전히 달라 별도의 관리가 필요하다는 비동기 관리의 핵심 전제이기 때문입니다 [1].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 캐싱 로딩, 에러 사이클, 서버 데이터 최신화 기법.
|
||||
|
||||
- 디바운싱(Debouncing) 및 쓰로틀링(Throttling)
|
||||
- 연결 이유: 잦은 사용자 이벤트로 인해 발생하는 무분별한 비동기 API 호출을 제어하여 성능을 최적화하는 구체적인 전략이기 때문입니다 [6, 7].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: 프론트엔드에서의 네트워크 최적화 및 런타임 병목 현상 방지.
|
||||
|
||||
- 클린업 (Cleanup) 함수와 메모리 누수
|
||||
- 연결 이유: 비동기 작업 완료 전 컴포넌트가 언마운트되었을 때 발생할 수 있는 자원 낭비와 메모리 누수를 막는 필수 규칙이기 때문입니다 [8, 9].
|
||||
- 이 개념을 통해 더 깊게 이해할 수 있는 부분: React 생명주기(Lifecycle)와 결합된 안전한 비동기 처리 방법.
|
||||
|
||||
### Deeper Research Questions
|
||||
- Zustand와 같은 가벼운 전역 상태 관리 라이브러리로 대규모 비동기 처리를 구현할 때 발생하는 아키텍처적 한계와 파편화 문제는 구체적으로 어떻게 나타나는가? [3, 5]
|
||||
- RTK Query가 제공하는 캐시 무효화(cache invalidation) 및 자동 데이터 재요청 기능의 내부 작동 방식은 무엇인가? [4]
|
||||
- TanStack Query를 활용하여 무한 스크롤 및 낙관적 업데이트를 구현할 때, 캐시 레이어는 어떻게 무결성을 보장하는가? [2]
|
||||
- Feature-Sliced Design 같은 모듈화된 폴더 구조에서 API 선언과 비동기 커스텀 훅은 어떤 방식으로 캡슐화되고 호출되는가? [2]
|
||||
- `useEffect` 내의 비동기 호출 시 메모리 누수를 잡기 위한 DevTools Heap Snapshot 분석 방법은 어떻게 적용되는가? [9]
|
||||
|
||||
### Practical Application Contexts
|
||||
- **Implementation:** UI 컴포넌트 내부에서 비동기 로직을 직접 구현하지 않고, API 요청을 처리하는 네트워크 로직을 커스텀 훅으로 추출하여 `features/` 폴더 하위에 격리하여 구현합니다 [2, 10]. 또한 `useEffect`를 통한 구독 시 클린업 함수를 반드시 적용합니다 [8].
|
||||
- **System Design:** 프로젝트 설계 시 클라이언트 전역 상태(예: UI 테마, 언어)와 서버에서 불러오는 비동기 상태(예: 사용자 데이터, 알림)를 완전히 분리하여 각기 다른 도구(Zustand + TanStack Query)를 사용하도록 설계합니다 [1, 11].
|
||||
- **Operation / Maintenance:** Redux DevTools와 같은 도구를 활용하여 비동기 액션이 언제 호출되었고 서버 데이터가 어떻게 업데이트되었는지 타임트래블 디버깅(Time-travel debugging)을 진행하여 문제를 신속히 파악합니다 [12, 13].
|
||||
- **Learning Path:** 컴포넌트 단위의 `useState`/`useEffect`를 통한 데이터 패칭의 한계 학습 → 디바운싱/메모리 누수 방지 원리 이해 → 서버 상태와 클라이언트 상태의 차이 인지 → TanStack Query/RTK Query를 통한 전문적인 비동기 상태 관리 마스터로 이어집니다 [1, 3, 7, 8].
|
||||
- **My Project Relevance:** 실시간 알림, 방대한 데이터의 무한 스크롤, 사용자 검색 시의 자동완성(디바운스 적용) 기능 등 복잡한 API 기반 기능이 있는 프로젝트의 성능 및 아키텍처 개선에 직접 적용됩니다 [2, 6, 7].
|
||||
|
||||
### Adjacent Topics
|
||||
- 상태 관리 아키텍처 (State Management Architecture)
|
||||
- 확장 방향: 비동기 데이터 관리(서버 상태)와 로컬 상태, 전역 애플리케이션 상태가 애플리케이션 내에서 어떻게 상호작용하고 조화롭게 구성되는지 확장해서 알아봅니다 [1, 14].
|
||||
- 성능 프로파일링 및 렌더링 최적화 (Performance Profiling & Rendering Optimization)
|
||||
- 확장 방향: 잘못된 비동기 데이터 처리가 어떻게 불필요한 리렌더링 폭풍(re-render storm)이나 병목을 일으키는지 파악하고, React Profiler를 통해 이를 어떻게 탐지하는지 알아봅니다 [15-17].
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-30*
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
|
||||
- **과거 데이터와의 충돌:** 없음
|
||||
- **정책 변화:** 없음
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
### 매 query lifecycle
|
||||
```
|
||||
fetching → fresh (staleTime) → stale → refetch → fresh
|
||||
↓
|
||||
gcTime → garbage collect
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
## 💻 패턴
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### TanStack Query v5 (React)
|
||||
```tsx
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
function UserProfile({ id }: { id: string }) {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['user', id],
|
||||
queryFn: ({ signal }) => fetch(`/api/users/${id}`, { signal }).then(r => r.json()),
|
||||
staleTime: 60_000, // 1min fresh
|
||||
gcTime: 5 * 60_000, // 5min cache
|
||||
});
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (error) return <Error error={error} />;
|
||||
return <Profile user={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### Mutation + invalidation
|
||||
```tsx
|
||||
function useUpdateUser() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (user: User) =>
|
||||
fetch(`/api/users/${user.id}`, {
|
||||
method: 'PUT', body: JSON.stringify(user),
|
||||
}).then(r => r.json()),
|
||||
onSuccess: (_, user) => {
|
||||
qc.invalidateQueries({ queryKey: ['user', user.id] });
|
||||
qc.invalidateQueries({ queryKey: ['users'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
### Optimistic update
|
||||
```tsx
|
||||
useMutation({
|
||||
mutationFn: toggleTodo,
|
||||
onMutate: async (id) => {
|
||||
await qc.cancelQueries({ queryKey: ['todos'] });
|
||||
const prev = qc.getQueryData(['todos']);
|
||||
qc.setQueryData(['todos'], (old: Todo[]) =>
|
||||
old.map(t => t.id === id ? { ...t, done: !t.done } : t));
|
||||
return { prev };
|
||||
},
|
||||
onError: (_, __, ctx) => qc.setQueryData(['todos'], ctx?.prev),
|
||||
onSettled: () => qc.invalidateQueries({ queryKey: ['todos'] }),
|
||||
});
|
||||
```
|
||||
|
||||
### Infinite scroll
|
||||
```tsx
|
||||
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||||
queryKey: ['posts'],
|
||||
queryFn: ({ pageParam = 0 }) =>
|
||||
fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
|
||||
getNextPageParam: (last) => last.nextCursor,
|
||||
initialPageParam: 0,
|
||||
});
|
||||
```
|
||||
|
||||
### Suspense mode (React 19)
|
||||
```tsx
|
||||
const { data } = useSuspenseQuery({
|
||||
queryKey: ['user', id],
|
||||
queryFn: fetchUser,
|
||||
});
|
||||
// data 의 always defined — Suspense boundary 의 loading 의 handle
|
||||
```
|
||||
|
||||
### SWR (lightweight alternative)
|
||||
```tsx
|
||||
import useSWR from 'swr';
|
||||
|
||||
const { data, error, mutate } = useSWR(
|
||||
`/api/users/${id}`,
|
||||
(url) => fetch(url).then(r => r.json()),
|
||||
{ revalidateOnFocus: true, dedupingInterval: 2000 }
|
||||
);
|
||||
```
|
||||
|
||||
### Server Components data (Next.js 15 / RSC)
|
||||
```tsx
|
||||
// app/users/[id]/page.tsx — runs on server
|
||||
async function UserPage({ params }: { params: { id: string } }) {
|
||||
const user = await fetch(`https://api/users/${params.id}`, {
|
||||
next: { revalidate: 60, tags: [`user-${params.id}`] }
|
||||
}).then(r => r.json());
|
||||
return <Profile user={user} />;
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| React app | TanStack Query v5 |
|
||||
| Next.js App Router | RSC fetch + Server Actions + tag invalidation |
|
||||
| Redux app | RTK Query (Redux 의 통합) |
|
||||
| 매 minimal bundle | SWR (~5KB) |
|
||||
| 매 GraphQL | Apollo Client / urql |
|
||||
| 매 simple form fetch | native `fetch` + `useState` 의 OK |
|
||||
|
||||
**기본값**: 매 React + REST → TanStack Query, 매 Next.js → RSC fetch.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[State Management]] · [[Data Fetching Patterns]]
|
||||
- 변형: [[TanStack Query]] · [[SWR]] · [[RTK Query]] · [[Apollo Client]]
|
||||
- 응용: [[Optimistic UI]] · [[Infinite Scroll]] · [[React Server Components]]
|
||||
- Adjacent: [[AbortController]] · [[Request Deduplication]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: cache key design 의 review, invalidation strategy 의 plan, race condition 의 debug.
|
||||
**언제 X**: real-time data (WebSocket/SSE 의 substitute).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **`useEffect + fetch`**: 매 race condition, 매 no dedup, 매 no cache — 매 library 의 use.
|
||||
- **Global Redux 에 server state**: 매 manual cache management — 매 RTK Query 의 use.
|
||||
- **Polling 의 abuse**: 매 SSE/WebSocket 의 substitute.
|
||||
- **`enabled: !!id` 누락**: 매 undefined 의 fetch — false positive.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (TanStack Query v5 docs, Vercel SWR docs, Next.js 15 data fetching).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — TanStack Query v5 + RSC + optimistic update 의 정리 |
|
||||
|
||||
Reference in New Issue
Block a user