DB 읽기 비용 최적화 및 파트너 운영 조회 재구성 계획
ADR-350 DB 읽기 비용 최적화 및 파트너 운영 조회 재구성 계획
1. Metadata
- ADR ID: ADR-350
- Status: draft
- Date: 2026-03-19
- Owner: YSY
- Related ADRs: ADR-220, ADR-270, ADR-280, ADR-290, ADR-310, ADR-340
- Supersedes: 없음
- Superseded By: 없음
2. Context
- 현재 비용 문서 기준으로 가장 먼저 커질 가능성이 높은 항목은 이미지 egress지만, 파트너 운영 화면의 DB 읽기 패턴도 장기적으로 compute, RLS 스캔, 응답 지연, Edge/RPC 호출 수를 함께 밀어 올릴 수 있다.
- 현재 파트너 운영 흐름은
partnerSlotsContext,usePartnerSubscriptionManager,usePartnerStoreEditor,usePartnerQrManager로 읽기 책임이 흩어져 있다. - 같은 화면에서 다음 성격의 읽기가 분산 호출된다.
- 파트너 목록/선택 파트너 해석
- 슬롯 목록 전체 로드
- 선택 파트너 entitlement 로드
- 구독 플랜/Addon 카탈로그 로드
- QR nonce/rotation 정보 로드
- 매장 편집 상세 정보 로드
- 이 구조는 기능별 분리는 되어 있지만, 화면 기준으로는
hot data와cold data가 섞여 있고, 일부 정적 카탈로그와 일부 대용량 상세 필드가 필요 이상 자주 읽힌다. - 비용 최적화 관점의 목표는 “무조건 쿼리 수를 줄이는 것”이 아니라 다음 4가지를 함께 만족하는 것이다.
- 동일한 사용자 행동당 DB round trip를 줄인다.
- RLS/조인/정렬이 큰 읽기를 덜 자주 실행한다.
- 화면 초기 진입에서 필요한 최소 데이터만 읽고 나머지는 지연 로드한다.
- 화면 간 로컬 상태와 서버 SoT를 유지하면서도 mutation 직후 재조회 비용을 억제한다.
3. Domain Decision
- 파트너 운영 영역의 읽기 모델은
화면 기준으로 재구성한다. 선택 파트너의 운영 요약과편집 상세는 같은 도메인이라도 다른 읽기 계약으로 분리한다.구독 카탈로그,파트너 요약,슬롯 목록,편집 상세,QR 메타데이터는 갱신 빈도가 다르므로 동일한 캐시 규칙을 사용하지 않는다.- 파트너 운영 화면은 기본적으로
운영 요약을 빠르게 보는 흐름을 우선하고, 편집은 명시적으로 진입했을 때만 상세 데이터를 읽는다. - mutation은 가능하면
변경 결과의 authoritative payload를 반환해야 하며, 성공 직후 전체 재조회는 기본 전략으로 사용하지 않는다.
4. Product Decision
- 파트너 운영 화면의 초기 로드는 “지금 확인해야 하는 정보” 중심으로 재정의한다.
- 파트너가 화면 진입 직후 꼭 봐야 하는 정보는 다음으로 제한한다.
- 선택 파트너 식별
- 구독 상태 요약
- QR 상태 요약
- 최근/활성 슬롯 상태
- 매장 편집용 상세 필드, 이미지 배열, 주소 수정 보조 데이터는 기본 진입 데이터에서 제외한다.
- 운영 화면에서 “수정 모드 미진입 상태”의 비용이 “편집 기능이 많은 상태”보다 우선 관리 대상이다.
5. UX Decision
- 파트너 운영 화면은
빠른 확인과깊은 편집을 구분한다. - 초기 진입 UX는 skeleton 또는 이전 캐시를 먼저 보여주고, 편집 상세는 모달 오픈 시점에 늦게 불러온다.
- 카탈로그성 데이터(
subscription_plans,subscription_addons)는 파트너 전환마다 다시 읽지 않고 세션 캐시를 우선 사용한다. - 슬롯 리스트는 기본적으로
활성 + 최근 종료 일부를 먼저 보여주고, 장기 이력은 별도 더보기 또는 페이지네이션으로 분리한다. - 사용자가 같은 파트너를 반복 방문할 때 화면이 매번 “처음부터 다 불러오는 느낌”이 들지 않도록 stale-while-revalidate 패턴을 허용한다.
6. Tech Decision
6.1 최적화 우선순위
중복/불필요 읽기 제거정적 카탈로그 캐시hot/cold 데이터 분리화면용 read model 또는 snapshot 통합이력성 목록의 범위 제한 및 페이지네이션
6.2 화면별 읽기 계약
Partner Manage초기 진입은 목표적으로 다음 읽기만 허용한다.- 파트너 선택 정보
- 선택 파트너 운영 요약
- 필요한 경우 슬롯 요약 또는 최근 슬롯 일부
파트너 편집 상세는 편집 진입 시점에 별도 읽기 계약으로 분리한다.QR nonce/rotated_at는 별도 단건 select 대신 운영 요약 모델에 합치는 것을 우선 검토한다.selectedPartnerEntitlement와QR 상태,파트너 기본 상태는 하나의 read model로 묶을 수 있으면 묶는다.
6.3 구체적 리팩토링 계획
Phase 0. 계측과 예산 고정
- 파트너 운영 화면별
cold load query budget과warm load query budget을 문서로 고정한다. - 1차 예산은 아래를 기준으로 둔다.
Partner Manage cold load: DB/RPC 최대 3회Partner Manage warm load: DB/RPC 최대 2회Partner Edit open: DB/RPC 최대 1회 추가Partner Slots open: 슬롯 목록 1회 + entitlement 캐시 재검증 0~1회- 측정 대상은
호출 수,평균 응답시간,응답 payload 크기,선택 파트너 변경 시 재호출 수다.
Phase 1. 중복 읽기 제거
useFocusEffect + mount effect로 같은 데이터를 두 번 읽는 패턴을 제거한다.- mutation 직후 화면 전체를 다시 읽는 패턴 대신, mutation 결과 row 또는 summary patch를 로컬 상태에 반영한다.
partnerSlotsContext에서 이미 가진 파트너 기본 정보와 다른 hook들이 다시 읽는 정보의 필드 겹침을 정리한다.
Phase 2. 정적/저변화 데이터 캐시
subscription_plans,subscription_addons는 파트너 선택과 무관한 세션 범위 캐시로 이동한다.- 카탈로그는 앱 세션당 1회 로드, 수동 무효화 또는 짧은 TTL 재검증으로 바꾼다.
- 카탈로그를 DB read 대신 번들 상수 또는 원격 구성으로 이동할지는
운영 변경 빈도를 본 뒤 결정한다.
Phase 3. hot/cold 데이터 분리
usePartnerStoreEditor.loadPartnerDetail()은 운영 화면 초기 진입에서 실행하지 않고, 편집 모달 오픈 시점으로 지연한다.photo_urls,primary_thumb_url,primary_detail_url,intro,address_detail같은 편집용 상세 필드는 cold data로 분류한다.selectedPartnerId,status,open_time,close_time,location_quality,entitlement,qr_nonce,qr_rotated_at는 hot data로 분류한다.
Phase 4. 운영 요약 read model 통합
- 다음 필드를 하나의
partner_operation_summaryread model로 통합 검토한다. - partner id/name/category/status/location_quality
- open_time/close_time
- entitlement state/can_issue/deny_reason
- qr_nonce/qr_rotated_at
- active slot count / recent closed slot count
- pending subscription change 여부
- 이 모델은
view또는rpc로 제공하되, 앱이 화면 진입 때 필수 정보를 여러 hook에서 따로 모으지 않게 한다. ADR-290의 snapshot 방향과 충돌하지 않도록, 집계가 무거운 항목은 실시간 summary가 아니라 snapshot 또는 최근 N개 범위 집계로 제한한다.
Phase 5. 슬롯 목록 범위 제한
- 현재
selected partner의 모든 슬롯을 한 번에 읽는 구조는active + recent closed기본 로드로 축소한다. - 기본 정렬과 조회 범위는 다음을 권장한다.
- 활성 슬롯 전체
- 종료 슬롯 최근 30일 또는 최근 50건
- 과거 전체 이력은 페이지네이션
- 필요한 인덱스는
partner_id + start_at,partner_id + status + start_at기준으로 재검토한다.
Phase 6. DB 계약 최적화
rotate_partner_qr,update_partner_subscription_plan,cancel_partner_subscription_plan_change,set_partner_subscription_addons,redeem_partner_subscription_coupon는 화면에 필요한 최소 summary를 반환하도록 표준화한다.- 읽기 후 쓰기 후 읽기 패턴 대신
write RPC -> summary return -> local patch흐름을 기본으로 한다. partner_users -> partners,partners,slots,subscription_*에 대해 화면 기준 select field를 최소화한다.- 파트너 목록 fallback 조회는 개발/관리 시나리오용인지 운영 기본 경로인지 분리 검토한다. 운영 기본 경로가 아니면 전체
partners풀스캔을 기본값으로 두지 않는다.
6.4 비채택 방향
- 비용 최적화 명목으로 Realtime을 성급히 도입해 상시 연결 비용과 복잡도를 늘리지 않는다.
- 아직 측정 없이 모든 읽기를 RPC 하나로 몰아넣지 않는다.
- 프런트엔드 로컬 상태만 늘려 서버 SoT를 흐리게 만드는 방식은 채택하지 않는다.
7. Ops Decision
- DB 비용 최적화는
월 청구만이 아니라화면당 호출 수와응답 크기를 같이 본다. - 운영 리포트에 최소 아래 항목을 추가한다.
- 화면별 평균 read 호출 수
- 화면별 P50/P95 응답 시간
- 카탈로그 read hit/miss
- 파트너 전환 시 추가 read 수
- 슬롯 목록 평균 반환 row 수
partner/manage,partner/slots,partner/edit open은 별도 예산 대상으로 관리한다.- read model 도입 전후로
DB invocation per active partner session을 비교한다. - 비용이 아니라 UX 문제로 보이는 지연도 결국 compute/scan 증가 신호일 수 있으므로 성능과 비용을 같은 운영 항목으로 본다.
8. Implementation Contract
8.1 Read Budget Contract
Partner Manage cold load는 목표적으로3회이내의 DB/RPC 호출로 제한한다.Partner Manage warm load는 목표적으로2회이내의 DB/RPC 호출로 제한한다.Partner Edit open은 목표적으로1회추가 읽기만 허용한다.subscription_plans,subscription_addons는 파트너 전환마다 다시 읽지 않는다.
8.2 Data Contract
- 운영 요약 모델은 최소
partner summary + entitlement + qr metadata + slot summary를 포함해야 한다. - 편집 상세 모델은
address/address_detail/intro/photo_urls/image_version/location을 포함한다. - 슬롯 기본 목록은
active + recent closed를 기본값으로 하고, 전체 과거 목록은 별도 페이지네이션 계약을 사용한다.
8.3 Cache Contract
- 카탈로그는 세션 캐시 또는 shared query cache를 사용한다.
- partner summary는 stale-while-revalidate를 허용한다.
- entitlement는 mutation 성공 시 summary patch 또는 명시적 refresh로 동기화한다.
8.4 Observability Contract
- 리팩토링 전후로 화면별 DB 호출 수를 비교할 수 있어야 한다.
- 쿼리 budget 초과는 개발 단계에서 로그 또는 QA 체크리스트로 확인 가능해야 한다.
9. Alternatives
대안 A. 현재 hook 분리 구조 유지 + 미세 수정만 적용
- 장점: 구현 공수가 가장 작다.
- 단점: 화면 기준 중복 읽기와 hot/cold 혼합 구조가 그대로 남는다.
- 판단: 1차 응급처치는 가능하지만 비용 최적화의 기준 구조로는 채택하지 않는다.
대안 B. 모든 읽기를 단일 초대형 RPC로 통합
- 장점: 앱 round trip 수는 가장 작아질 수 있다.
- 단점: DB 함수가 비대해지고 필드 결합도가 높아져 변경 비용이 커진다.
- 판단:
측정 후필요한 summary 범위에 한해 제한적으로만 채택한다.
대안 C. 전면적 클라이언트 캐싱 우선
- 장점: 즉시 체감 성능은 좋아질 수 있다.
- 단점: stale state, 권한/구독 불일치, mutation 후 정합성 문제를 키울 수 있다.
- 판단: 서버 SoT를 유지하는 범위에서만 제한적으로 채택한다.
10. Consequences / Impact
긍정 영향:
- 같은 화면 진입당 DB/RPC 호출 수를 줄여 compute와 RLS 스캔 비용을 완화할 수 있다.
- 화면 초기 응답이 빨라져 UX와 비용 최적화가 같은 방향으로 움직인다.
- 카탈로그, 요약, 상세를 분리하면 이후 캐시 정책과 권한 경계도 더 명확해진다.
- mutation 후 전체 재조회 의존이 줄어 네트워크와 DB 부담이 함께 내려간다.
부정 영향:
- read model과 상세 모델을 분리하면 코드 경계가 늘어나 초기 리팩토링 난이도가 올라간다.
- summary/view/rpc 도입 시 DB 계층 문서와 테스트를 같이 관리해야 한다.
- 캐시 정책을 잘못 잡으면 stale data나 화면 간 불일치가 생길 수 있다.
11. Scope
App/state/partnerSlotsContext.tsxApp/hooks/usePartnerSubscriptionManager.tsApp/hooks/usePartnerStoreEditor.tsApp/hooks/usePartnerQrManager.ts- 파트너 운영 관련 SQL view/RPC/index
- 운영 계측 및 비용 리포트 문서
12. Validation
-
partner/manage,partner/slots,partner edit open의 현재 read 호출 수를 측정했다. - 정적 카탈로그가 파트너 전환마다 재조회되지 않도록 설계했다.
- 운영 화면 초기 로드에서 cold data를 제거하는 설계가 반영되었다.
- 슬롯 목록 기본 범위 제한 또는 페이지네이션 계약이 정의되었다.
-
ADR-290,ADR-310,TECH_COST_SCENARIOS.md와 충돌하지 않는다. - 리팩토링 전후를 비교할 운영 지표가 정의되었다.
13. Rollout / Migration
- 1차: 계측 추가와 budget 문서화
- 2차: 카탈로그 캐시와 중복 로드 제거
- 3차: 편집 상세 지연 로드
- 4차: 운영 요약 read model 도입
- 5차: 슬롯 이력 페이지네이션/범위 제한
- 6차: DB index/RPC 반환 계약 정리
14. Open Questions
- 운영 요약을
view로 둘지rpc로 둘지 어느 쪽이 RLS/권한 모델에 더 단순한가 - 구독 카탈로그를 DB SoT로 유지할지, 릴리스 단위 정적 상수로 옮길지
- 슬롯 목록의 기본 범위를
30일로 볼지최근 50건으로 볼지 - 개발/운영 환경에서
partners전체 fallback 조회를 계속 허용할지