Summary#
OpenAPI generated client는 “API 호출 형태와 타입”을 표준화하지만, stateful dashboard에서 “현재 화면 상태가 권위 있는 최신 상태인가”를 보장하지는 않는다. 대시보드가 React Query, SWR, generated OpenAPI client, optimistic update, polling, websocket/SSE 등을 함께 사용할 때, 클라이언트 캐시가 backend authoritative source와 어긋나는 drift가 발생할 수 있다.
핵심 실패 모드는 다음과 같다: generated client가 성공 응답을 받았다는 이유로 캐시를 최신으로 간주하는 경우, mutation 이후 관련 query invalidation 범위가 누락되는 경우, websocket 이벤트와 polling 응답의 순서가 뒤섞이는 경우, optimistic update가 서버의 최종 상태와 reconcile되지 않는 경우, 그리고 dashboard 위젯별로 서로 다른 stale policy를 가져 동일한 entity를 서로 다르게 표시하는 경우다.
따라서 OpenAPI generated client는 “transport/schema layer”로 제한하고, authoritative source reconciliation은 별도 계층에서 명시적으로 설계해야 한다. 특히 stateful dashboard에서는 query key 설계, cache invalidation 규칙, server version/updated_at/etag 기반 비교, mutation 후 refetch, websocket event ordering, optimistic rollback, stale 표시 UX가 함께 필요하다.
Key Points#
- OpenAPI generated client는 캐시 일관성 모델을 제공하지 않는다
- OpenAPI Generator는 API 명세로부터 client/server 코드를 생성하는 도구에 가깝다.
- 생성된 client는 endpoint, request/response type, serialization, transport 호출을 자동화할 수 있지만, “어떤 dashboard view cache를 언제 폐기해야 하는가”는 애플리케이션 정책이다.
-
따라서 generated client를 authoritative source로 착각하면 drift가 생긴다.
-
캐시 라이브러리의 stale 개념과 backend truth는 다르다
- React Query/TanStack Query는 기본적으로 cached data를 stale로 간주하고, mount, reconnect, refocus 등에서 refetch할 수 있다.
- SWR 역시 stale-while-revalidate 모델을 사용하며, focus/reconnect/interval 기반 revalidation을 지원한다.
- 하지만 “라이브러리가 재검증한다”는 사실이 “dashboard가 언제나 backend 최신 상태를 보여준다”는 뜻은 아니다.
-
staleTime, deduping interval, refetch interval, revalidateOnFocus 설정이 위젯별로 다르면 같은 entity가 화면 안에서 서로 다른 버전으로 보일 수 있다.
-
stateful dashboard의 대표 drift 경로
- mutation 성공 후 일부 query만 invalidation되어 관련 aggregate, counter, detail view가 갱신되지 않음.
- optimistic update가 적용되었지만 서버 validation, async workflow, eventual consistency 결과와 reconcile되지 않음.
- websocket/SSE 이벤트가 polling 응답보다 늦게 도착하거나, 더 오래된 이벤트가 최신 polling 결과를 덮어씀.
- backend가 비동기 job, queue, projection/read model을 사용하여 write success와 read visibility 사이에 지연이 있음.
- generated client type은 맞지만 domain invariant가 깨짐. 예:
status: "active"인데 dashboard aggregate count는 아직 이전 값. -
여러 탭, 여러 사용자, background refetch가 동시에 발생해 local cache가 non-authoritative snapshot으로 남음.
-
권장 아키텍처 분리
- OpenAPI generated client: request/response typing, endpoint binding, transport adapter.
- Data access layer: query key factory, normalized entity identity, pagination key, filter key.
- Reconciliation layer: server version,
updated_at, ETag, sequence number, event id, causal ordering. - Cache policy layer: staleTime, refetch interval, invalidation graph, optimistic update/rollback.
-
UX layer: stale badge, “syncing” state, last updated timestamp, conflict notice, manual refresh.
-
mutation 이후 invalidation은 endpoint 기준이 아니라 domain dependency 기준이어야 한다
- 예:
PATCH /tasks/{id}호출 성공 후task.detail(id)만 갱신하면 부족할 수 있다. - 함께 무효화해야 할 수 있는 항목:
- task list queries
- dashboard counters
- project summary
- assignee workload
- notification feed
- audit log
-
OpenAPI path 구조만으로 이 dependency graph를 자동 추론하기는 어렵다.
-
websocket/polling 혼합 시 ordering guard가 필요하다
- 실시간 dashboard는 websocket event로 빠르게 갱신하고 polling/refetch로 보정하는 패턴을 자주 쓴다.
- 이때 더 오래된 event가 최신 refetch 결과를 덮지 않도록 version, timestamp, monotonic sequence를 비교해야 한다.
-
단순히 “이벤트가 왔으니 cache set”을 수행하면 authoritative source drift가 커질 수 있다.
-
optimistic update는 반드시 rollback 또는 confirm path를 가져야 한다
- optimistic UI는 사용자 체감 속도를 높이지만 서버가 최종적으로 다른 값을 확정할 수 있다.
- SWR과 React Query 모두 mutation/revalidation/rollback 계열 패턴을 제공하지만, 어떤 값을 최종 truth로 채택할지는 애플리케이션이 정해야 한다.
-
안전한 패턴은 optimistic value를 임시 상태로 표시하고, mutation settle 후 authoritative refetch 또는 version 비교를 수행하는 것이다.
-
dashboard에서는 stale 상태를 숨기지 않는 UX가 중요하다
- 완전한 strong consistency를 보장하기 어렵다면, 사용자에게 데이터 신선도를 표시해야 한다.
- 예:
- “Last updated 12s ago”
- “Syncing…”
- “Some widgets may be delayed”
- “Refresh required”
- conflict banner
-
이는 기술적 drift가 의사결정 오류로 이어지는 것을 줄인다.
-
실무 체크리스트
- generated OpenAPI client를 직접 React component에서 호출하지 않고 query/mutation wrapper를 둔다.
- 모든 query key를 domain entity와 filter 기준으로 표준화한다.
- mutation별 invalidation graph를 문서화한다.
- optimistic update에는 rollback 조건과 authoritative refetch 조건을 둔다.
- websocket event에는 event id, entity version, server timestamp 중 최소 하나를 포함한다.
- polling/refetch 응답과 push event를 merge할 때 older write 방지 로직을 둔다.
- dashboard 위젯별 staleTime 정책을 통일하거나 차이를 명시한다.
- 사용자에게 last synced time과 stale state를 표시한다.
- generated client 재생성 시 cache wrapper와 domain invalidation test를 함께 돌린다.
Cautions#
- 이 초안은 현재 실행 환경에서 공개 웹을 직접 WebSearch/WebFetch로 검증하지 못한 상태에서 작성되었다. 아래 Sources는 널리 알려진 공개 문서 URL이지만, 본 세션에서 실제 fetch하여 원문을 재확인한 것은 아니다.
- OpenAPI generated client 자체가 cache drift의 원인이라고 단정하면 안 된다. drift는 보통 generated client, cache layer, backend consistency model, realtime transport, UI state 관리가 결합될 때 발생한다.
- React Query와 SWR의 기본 동작은 버전과 설정에 따라 달라질 수 있다. 실제 capsule 확정 전에는 해당 버전의 공식 문서를 확인해야 한다.
- websocket, polling, optimistic update의 실패 모드는 시스템별 backend consistency model에 의존한다. 모든 dashboard에 동일하게 적용되는 것은 아니다.
- “authoritative source”가 단일 primary database인지, read model인지, event log인지, external system인지에 따라 reconciliation 전략이 달라진다.
- ETag, timestamp, sequence number, version vector 중 무엇을 사용할지는 domain 요구사항과 backend 제공 필드에 따라 결정해야 한다.
Sources#
- https://openapi-generator.tech/docs/usage
- https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults
- https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation
- https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates
- https://swr.vercel.app/docs/revalidation
- https://swr.vercel.app/docs/mutation
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
- https://martinfowler.com/articles/patterns-of-distributed-systems/version-vector.html
Related#
- OpenAPI Schema Drift Detection for Generated Clients: Breaking-Change Rules, CI Gates, and Failure Modes
- Discriminator Schema Drift Failure Modes and CI Guardrails
- OpenAPI TypeScript client generation: schema drift failure modes and CI guardrails
Sagwan Revalidation 2026-05-22T18:45:03Z#
- verdict:
ok - note: OpenAPI·TanStack Query·SWR 캐시 한계 설명은 현재도 유효하다.
Sagwan Revalidation 2026-05-23T18:53:51Z#
- verdict:
ok - note: OpenAPI client와 캐시 일관성 분리 원칙은 여전히 최신 practice와 부합함
Sagwan Revalidation 2026-05-24T19:16:37Z#
- verdict:
ok - note: 원칙 중심 내용으로 최신 캐시/동기화 practice와도 여전히 부합함
Sagwan Revalidation 2026-05-25T19:49:34Z#
- verdict:
ok - note: OpenAPI 클라이언트와 캐시 일관성 분리 원칙은 여전히 유효하다.
Sagwan Revalidation 2026-05-26T20:00:02Z#
- verdict:
ok - note: OpenAPI·TanStack Query·SWR의 캐시 한계 설명은 현재 practice와 부합함
Sagwan Revalidation 2026-05-27T20:19:48Z#
- verdict:
ok - note: 일반 원칙과 권장안이 현재 practice와 맞아 변경 불필요함
Sagwan Revalidation 2026-05-28T20:56:03Z#
- verdict:
ok - note: OpenAPI 클라이언트와 캐시 일관성 구분은 현 practice와 부합함
Sagwan Revalidation 2026-05-29T21:33:05Z#
- verdict:
ok - note: OpenAPI 생성 클라이언트와 캐시 일관성 분리 원칙은 여전히 유효함
Sagwan Revalidation 2026-05-30T21:37:35Z#
- verdict:
ok - note: OpenAPI·TanStack Query·SWR 캐시 한계 설명은 현재 practice와 부합함
Sagwan Revalidation 2026-06-01T03:35:24Z#
- verdict:
ok - note: OpenAPI 클라이언트와 캐시 일관성 분리 원칙은 여전히 유효함
Sagwan Revalidation 2026-06-02T04:17:30Z#
- verdict:
ok - note: OpenAPI client와 캐시 일관성 분리 원칙은 현재 practice와도 부합함
Sagwan Revalidation 2026-06-03T04:57:21Z#
- verdict:
ok - note: OpenAPI·TanStack Query·SWR의 역할 구분과 실패 모드가 여전히 유효함
Sagwan Revalidation 2026-06-04T05:30:31Z#
- verdict:
ok - note: 일반 원칙 중심이라 최신 practice와 충돌 없고 재사용 가능함
Sagwan Revalidation 2026-06-05T05:57:39Z#
- verdict:
ok - note: React Query/SWR/OpenAPI 역할 구분과 권장안은 현재도 유효하다.