Files
clean-architecture-frontend…/docs/operations/client-cache-and-storage-recovery.md
T

204 lines
9.2 KiB
Markdown

# Client cache and Web Storage recovery
- 적용 대상: production-composed TanStack memory cache, registered Web Storage,
cross-tab invalidate-only runtime
- 비대상: query persistence와 IndexedDB query cache
- 기준일: 2026-07-28
현재 query persistence는 정책과 runtime에서 `disabled`다. 기존 IndexedDB
reference runtime은 query cache에 조립되지 않았으므로 query-cache incident에서
database 삭제, migration 또는 hydration 조치를 수행하지 않는다.
session/account Query lifecycle, strict query policy와 Web Storage v2 lifecycle도
현재 `DESIGNED_NOT_IMPLEMENTED`다. 아래 목표 절차를 현재 runtime의 보장으로
해석하지 않는다.
## 1. 변경할 수 없는 복구 원칙
- 서버가 server state와 authorization의 source of truth다.
- cross-tab event는 invalidate hint다. delivery나 server commit 증명이 아니다.
- remote event로 `queryClient.clear()`, account logout 또는 credential 폐기를
수행하지 않는다.
- `BroadcastChannel` 실패 후 localStorage fallback도 실패하면
`DEGRADED_LOCAL_ONLY`가 정상 fallback이다. 현재 tab의 local invalidation과
server request는 계속 동작해야 한다.
- origin 전체 `localStorage.clear()``deleteDatabase()`를 복구 명령으로
사용하지 않는다.
- query key, storage value, event/topic/epoch/source ID, user/account/tenant ID를
incident log에 복사하지 않는다.
- incident 대응 중 `QUERY_PERSISTENCE`를 켜거나 server response를 Web Storage에
저장하지 않는다.
## 2. 최초 확인
1. 영향 release ID, browser family/version과 최초 발생 시각을 확인한다.
2. `cache.operation.failed``storage.operation.failed`의 allowlisted
operation/outcome/reason만으로 범위를 좁힌다.
3. runtime status가 `ACTIVE_BROADCAST`, `ACTIVE_STORAGE_FALLBACK`,
`DEGRADED_LOCAL_ONLY`, `CLOSED` 중 무엇인지 확인한다.
4. 현재 tab의 mutation 성공 후 local namespace가 stale 처리되는지 확인한다.
5. 다른 tab의 오래된 화면이 단순 stale 표시인지, 실제 server authorization
우회인지 분리한다. authorization 우회는 cache incident가 아니라 P1 security
incident다.
6. affected release의 query registry topic/version과 release cache epoch가
일치하는지 확인한다.
## 3. Cross-tab invalidation degradation
### 신호
- `BROADCAST_OPEN_FAILED`
- `BROADCAST_PUBLISH_FAILED`
- `STORAGE_LISTENER_FAILED`
- `STORAGE_PUBLISH_FAILED`
- runtime status `DEGRADED_LOCAL_ONLY`
### 확인
1. browser policy, embedded/sandbox context 또는 privacy mode가
BroadcastChannel/Web Storage를 제한하는지 확인한다.
2. BroadcastChannel publish 실패 뒤 fixed localStorage pulse key로 정확히 한 번
fallback하는지 확인한다.
3. publishing tab은 `storage` event를 받지 않으므로 local invalidation을
coordinator가 직접 수행했는지 확인한다.
4. 다른 tab은 focus/reconnect 또는 명시적 refresh에서 server를 다시 읽는지
확인한다.
### 안전한 조치
- local-only 상태에서는 사용자에게 현재 tab refresh action을 유지한다.
- fallback regression이 특정 release에서 시작됐으면 이전 compatible release로
rollback한다.
- event payload에 query key/data를 추가하거나 TTL/size/source tracking limit을
임시 확대하지 않는다.
- 현재 runtime에는 별도 dynamic kill switch가 없다. 존재하지 않는 flag로
복구됐다고 선언하지 않는다.
## 4. Duplicate, sequence gap과 refetch 증가
### 신호
- `DUPLICATE`, `STALE`, sequence-gap observation 증가
- 여러 namespace의 active refetch 동시 증가
- backend read traffic 증가
### 확인
1. 같은 source/epoch의 sequence gap인지 새 page epoch의 정상 sequence reset인지
구분한다.
2. duplicate가 BroadcastChannel과 storage fallback 양쪽에서 들어온 것인지
확인한다.
3. mutation lease가 유지되는 동안 remote hint가 topic별로 coalesce되는지
확인한다.
4. gap에서 inactive query까지 즉시 refetch하거나 remote hint를 다시 publish하는
echo가 없는지 확인한다.
### 안전한 조치
- gap은 등록 namespace를 stale 처리하고 active query만 refetch한다.
- backend가 압박을 받으면 API degradation runbook의 server read 보호 정책을
적용한다. client query와 HTTP 양쪽 retry를 동시에 늘리지 않는다.
- persistent event queue나 localStorage counter를 급히 추가하지 않는다.
## 5. Web Storage quota, corruption과 denial
### 신호
- `STORAGE_QUOTA_EXCEEDED`
- `SIZE_LIMIT_EXCEEDED`
- `VALUE_REJECTED`
- `STORAGE_UNAVAILABLE`
### 확인
1. 실패 key가 registry backend/value codec/schema/TTL과 일치하는지 확인한다.
2. value가 기본 16,384-byte hard cap 안인지 확인한다.
3. corrupt/expired record가 exact physical key에서만 제거되고 다른 application
key는 유지되는지 확인한다.
4. `COLOR_SCHEME` quota fallback이 동일 serialized envelope와 TTL 규칙을 쓰는지
확인한다.
5. native exception message나 value가 diagnostics에 포함되지 않았는지 확인한다.
### 안전한 조치
- preference persistence 실패는 memory fallback 또는 safe default로 degrade한다.
- exact corrupt/expired registered key만 제거한다.
- credential key를 임시 storage key로 재등록하지 않는다.
- origin 전체 clear, arbitrary key enumeration과 query-cache persistence 전환을
금지한다.
## 6. Logout, account 전환과 stale data
현재 구현은 release cache epoch만 제공하고 session/account epoch와 opaque account
partition은 아직 없다. 따라서 다음을 과장해 보장하지 않는다.
- cross-tab hint가 모든 tab의 logout을 완료했다.
- old in-flight result가 새 account runtime에 기록되지 않는다.
- persisted query data가 account별로 분리된다.
실제 account-switching 제품을 배포하기 전에 session owner 기반 local admission
fence, old QueryClient cancel/clear, late-result generation fence와 account epoch를
구현해야 한다. 그 전에는 external auth owner와 서버 authorization을
authoritative하게 유지하고, 의심되는 stale personal data incident는 security
owner에게 escalation한다.
VD-13 lifecycle이 구현된 제품의 안전한 전환 순서는 다음과 같다.
```text
local session authority revoke
-> old generation FENCED
-> new query/mutation admission 거절
-> in-flight cancel
-> provider/listener detach
-> old QueryClient clear + dispose
-> old Web Storage/optional persistence partition purge policy
-> 새 opaque scope와 새 QueryClient remount
```
각 async callback은 terminal cache write 직전에 captured generation을 다시
검증한다. broadcast logout 수신 여부와 무관하게 local 전환이 완료되어야 한다.
optional query persistence를 선택한 제품은 durable namespace epoch/CAS와 old
record resurrection fault까지 통과한 경우에만 restore를 다시 연다.
## 7. Cleanup과 종료
정상 dispose는 coordinator subscription, BroadcastChannel, storage listener,
dedupe/high-watermark state와 pending remote set을 idempotent하게 정리한다.
확인:
- HMR/unmount 반복 뒤 listener/channel 수가 증가하지 않음
- dispose 뒤 event가 query invalidation을 실행하지 않음
- fixed pulse key가 publish 후 best-effort 제거됨
- cleanup failure가 cached value나 identifier를 log하지 않음
## 8. 복구 완료 조건
- current tab의 committed mutation 후 local namespace invalidation 성공
- available transport에서는 two-tab remote invalidation 성공
- unavailable transport에서는 explicit `DEGRADED_LOCAL_ONLY`와 manual/focus
revalidation 성공
- duplicate/self/stale event가 추가 refetch를 만들지 않음
- sequence gap은 bounded active-query reconciliation으로 종료
- Web Storage corrupt/quota scenario가 exact-key cleanup 또는 documented fallback
으로 종료
- raw key/value/query/user identifier가 diagnostic evidence에 없음
- affected browser의 manual two-tab smoke 기록 보존
two-page native cross-context transport suite는 존재하지만 현재 보존 evidence는
Chromium/Firefox에 한정되고, production QueryClient/coordinator의 account
lifecycle과 late-result fence까지 연결한 end-to-end case는 아직 없다. 따라서
manual smoke나 transport-only suite를 production promotion evidence의 영구
대체물로 사용하지 않는다. Chromium/Firefox/WebKit에서 scope 전환을 포함한
동일 case set이 통과할 때 이 runbook의 promotion close criteria를 충족한다.
## 9. 관련 문서
- [API contract와 Server State recovery](./api-contract-and-server-state-recovery.md)
- [VD-25 Server State Cache lifecycle](../architecture/decisions/VD-25-server-state-cache-lifecycle.md)
- [Client cache and browser storage platform](../architecture/client-cache-and-storage.md)
- [Browser data capability completion ledger](../architecture/browser-data-capability-completion-ledger.md)
- [VD-13 Client cache scope and persistence](../architecture/decisions/VD-13-client-cache-scope-and-persistence.md)
- [TypeScript, 상태 소유권, 데이터 흐름](../architecture/typescript-state-and-data-flow.md)
- [Backend API degradation](../runbooks/FE-RB-003.md)
- [Release, cache, and rollback contract](./release-cache-rollback.md)