diff --git a/docs/data-patterns/improvements-backend-reads.md b/docs/data-patterns/improvements-backend-reads.md index 230f853..1afd745 100644 --- a/docs/data-patterns/improvements-backend-reads.md +++ b/docs/data-patterns/improvements-backend-reads.md @@ -15,6 +15,8 @@ ## R1. `dailyArchive` 날짜별 `listByDate` 공유 +> **요약**: 기존에는 아카이브 run에서 `judgeDay`와 `hasMissedGameDayBetween`가 **유저마다** 동일 날짜의 `games` 범위쿼리(`listByDate`)를 재실행하는 방식이라 `U × (1 + 최대 14)`회로 read가 U배 증폭됐으므로, run당 1개의 메모이징 캐시(`GameDayCache`)를 생성해 유저 루프 전체와 look-back 루프에 공유 주입함으로써 distinct 날짜당 1회로 수렴시키는 방식으로 수정했다. + ### 문제 (Before) - 아카이브 1회 run에서 `judgeDay`와 `hasMissedGameDayBetween`가 **유저마다** 동일 날짜의 `games` 범위쿼리(`listByDate`)를 재실행했다. - 유저당 비용: `judgeDay`의 대상 날짜 1회 + `hasMissedGameDayBetween`의 직전 결석 판정 look-back **최대 14회** = 최대 15회. @@ -37,6 +39,8 @@ ## R4. 캐시 스탬피드 폴링 read 완화 +> **요약**: 기존에는 락 보유자가 캐시를 채울 때까지 대기하는 3개 폴링 루프가 모두 **고정 500ms 간격**으로 폴링하는 방식이라 25s 윈도우 안에서 대기 요청마다 ~50회씩 read가 발생해 락 경합·콜드스타트 버스트에서 read가 증폭됐으므로, 공유 헬퍼 `backoffDelayMs`로 지수 백오프를 적용해 폴링 시도를 ~8~9회로 줄이는 방식으로 수정했다. + ### 문제 (Before) - 락 보유자가 캐시를 채울 때까지 대기하는 3개 폴링 루프가 모두 **고정 500ms 간격**으로 폴링했다. - `kboCacheRepository.waitForCache` (≤25s → 최대 ~50회 `getCached`), @@ -65,6 +69,8 @@ ## R5. 스코어보드 self-user read field-mask +> **요약**: 기존에는 `getScoreboard`가 본인 정보 계산에 `getUser(uid)`로 **전체 user doc**을 읽어 실제 사용하는 5개 필드 외 streak/티켓/notifications까지 폴링성 요청마다 전송하는 방식이라 bandwidth 낭비였으므로, `fieldMask`로 5개 필드만 페치하는 `getUserForScoreboard`를 도입하는 방식으로 수정했다. + ### 문제 (Before) - `getScoreboard`가 본인 정보 계산에 **전체 user doc**을 `getUser(uid)`로 읽었다(`scoreboardService.ts:54`). 실제 사용 필드는 5개뿐인데 streak/티켓/notifications 등 전체 doc을 전송받아 bandwidth 낭비. - 랭킹 화면은 폴링성 read(`Cache-Control: private, max-age=60`)라 요청마다 발생 (`backend-reads.md:86,168`). @@ -83,6 +89,8 @@ ## R7. 다년도 rank 병렬 fetch +> **요약**: 기존에는 `fetchRankFromKbo(years)`가 `for` 루프로 연도마다 `getOrFetch`를 **순차 await**하는 방식이라 연도별 캐시 키가 독립적인데도 동시 miss 시 외부 KBO fetch가 직렬화되어 다년도 조회 지연이 `N×T`로 누적됐으므로, `Promise.all`로 연도별 fetch를 병렬 실행해 `~1×T`로 단축하는 방식으로 수정했다. + ### 문제 (Before) - `fetchRankFromKbo(years)`가 `for` 루프로 연도마다 `getOrFetch`를 **순차 await**했다(`kboRepository.ts:46`). 연도별 캐시 키(`rank__{year}`)가 독립적인데도 직렬 대기. - 캐시 히트면 무해하나, **동시 miss(콜드/만료) 시 외부 KBO fetch가 직렬**이라 다년도 조회 지연 = N × 단년 지연 (`backend-reads.md:40,171`).