MSG-460 전체 지역 목록 개인화 커서 페이징¶
티켓: MSG-460 · 작성일: 2026-08-22 · 작성: prd-writer 상태: 검토됨 (2026-08-22 사용자 정정 승인)
1. 문제 상황¶
검색 화면의 무입력 상태에 표시되는 전체 지역 목록이 콘텐츠가 있는 전국 행정동을 한 번에 모두 내려준다. 현재 화면은 응답이 끝날 때까지 오래 기다리고, 데이터가 늘수록 조회와 전송 비용도 계속 커진다. 기존 MSG-460은 이 화면이 아닌 개인 도감의 행정동별 격자 목록을 대상으로 잘못 구현됐다.
2. 목적 · 목표¶
- 목적: 전체 지역 목록의 한 번 조회 비용을 고정하고, 로그인 사용자가 직접 최근 업로드한 지역부터 탐색하게 한다.
- 목표:
GET /api/regions/explore가 행정동을 한 번에 최대 20개씩 반환한다.- 사용자가 직접 업로드한 지역은 가장 최근 업로드 시각 순으로 먼저 보인다.
- 나머지 지역은 전역 공개 콘텐츠 격자 수가 많은 순으로 보인다.
- 다음 묶음을 커서로 이어서 조회할 수 있다.
- 비목표(스코프 제외):
- 행정동별 격자 카드 API
GET /api/regions/{regionCode}/grids는 바꾸지 않는다. - 개인 도감 격자 목록의 정렬, 상한, 응답 형상은 바꾸지 않는다.
- 전체 지역에 콘텐츠가 없는 행정동을 새로 노출하지 않는다.
3. 기능 요구사항¶
| ID | 요구사항 | 우선순위 |
|---|---|---|
| FR-1 | 로그인 사용자는 전역 공개 콘텐츠가 있는 행정동을 한 번에 최대 20개씩 조회할 수 있다 | Must |
| FR-2 | 로그인 사용자가 직접 영상을 업로드한 적이 있고 현재 개인 점령이 남은 지역을 나머지 지역보다 먼저 표시한다 | Must |
| FR-3 | 우선 지역끼리는 사용자의 마지막 업로드 시각 내림차순으로 정렬하고, 같은 시각이면 전역 공개 격자 수 내림차순, 다시 같으면 행정동 코드 오름차순으로 고정한다 | Must |
| FR-4 | 사용자의 직접 업로드 이력이 없는 나머지 지역은 전역 공개 격자 수 내림차순, 같은 수면 행정동 코드 오름차순으로 정렬한다 | Must |
| FR-5 | 사용자가 직접 업로드한 지역이 하나도 없으면 전체 목록을 전역 공개 격자 수 내림차순으로 정렬한다 | Must |
| FR-6 | 응답은 현재 묶음, 다음 묶음 존재 여부, 다음 요청에 그대로 사용할 불투명 커서를 제공한다[^1] | Must |
| FR-7 | 데이터가 바뀌지 않는 동안 커서를 이어서 조회하면 행정동이 중복되거나 누락되지 않는다 | Must |
| FR-8 | 전역 공개 콘텐츠가 없으면 200과 빈 페이지를 반환한다 | Must |
| FR-9 | 기존에 잘못 추가한 개인 도감 페이지 API를 제거하고, 개인 도감의 기존 무제한 행정동 조회 계약을 복원한다 | Must |
4. 비기능 요구사항¶
| 분류 | 요구사항 |
|---|---|
| 성능 | 집계 쿼리의 최종 결과를 21행으로 제한해 20행과 다음 묶음 존재 여부를 결정한다. 전체 건수 조회를 추가하지 않고 응답 본문은 최대 20개 항목으로 제한한다. 전역 집계 자체의 비용은 배포 전 실행 계획으로 확인한다. 확인 결과 이 조건은 충족하지 못했다 (2026-08-22 dev 실측: 첫 페이지 3,833 ms, 깊은 페이지 3,675 ms로 페이지 깊이와 무관하게 전역 집계가 매번 반복된다). 응답 본문 크기 제한은 달성했으나 집계 비용 제한은 달성하지 못했고, 그 부분은 MSG-461로 분리했다. 이 미충족을 안고 배포하는 것은 2026-08-22 팀원 K 판단이다. 수용하는 영향은 검색창을 눌렀을 때 뜨는 기본 목록이 약 3.7초 걸린다는 것이고, MSG-461을 다음 스프린트에 넣어 해소하는 것을 조건으로 한다. 정정 (2026-08-25, MSG-461 분해 실측, 팀원 K 승인) — 위에 적은 "전역 집계가 매번 반복되는 것이 원인"은 잘못된 진단이었다. 쿼리를 조각내 다시 재 보니 집계 자체는 257 ms이고, 6.8초 중 나머지는 regions 조인을 집계보다 앞에 둬서 행정동 조회가 144,536번 돈 것(Memoize loops=144536)과, 그룹 키가 (region_code, region_name)으로 넓어 정렬이 디스크로 샌 것(external merge Disk: 2312kB)이었다. 따라서 처방도 "집계를 미리 계산해 두기"가 아니라 "조인을 집계 뒤로 옮기기"로 바뀐다. 집계를 요청마다 도는 것 자체는 허용한다.승인하는 성능 목표 — 이 목록의 첫 페이지와 깊은 페이지 응답이 500 ms 미만이고, 비용이 페이지 깊이에 따라 늘지 않는다(MSG-461 실측 272~345 ms). 집계가 격자 수에 비례하는 것은 남는 한계이므로, 격자 50만 초과 또는 이 엔드포인트 p95 1초 초과 시 미리 계산(머티리얼라이즈드 뷰)으로 승격한다 |
| 보안/인가 | 토큰이 필요하다. 정렬 개인화에는 로그인 사용자의 업로드 이력만 사용하며 다른 계정에서 발급된 커서는 거절한다 |
| 데이터 정합 | 전역 격자 수는 기존 ACTIVE, PUBLIC, READY 기준을 유지한다. 개인 우선순위는 user_grids.last_uploaded_at을 행정동별 최댓값으로 계산한다 |
| 운영 | 읽기 계약 변경이며 마이그레이션은 없다. 기존 배열 응답 소비자는 페이지 응답으로 전환해야 한다 |
5. 시퀀스 다이어그램¶
6. 클래스 다이어그램¶
7. 변경 파일 목록¶
| 파일 | 변경 | Owner |
|---|---|---|
src/main/java/com/msg/fillmap/video/controller/RegionExploreController.java |
전체 지역 API에 사용자와 커서 입력, 페이지 응답 적용 | B |
src/main/java/com/msg/fillmap/video/service/RegionExploreService.java |
개인화 페이지 조회 계약 적용 | B |
src/main/java/com/msg/fillmap/video/service/RegionExploreServiceImpl.java |
20개와 다음 커서 조립 | B |
src/main/java/com/msg/fillmap/video/repository/VideoRepository.java |
사용자별 최근 업로드와 전역 격자 수를 결합한 키셋 조회 | B |
src/main/java/com/msg/fillmap/video/dto/RegionExplorePageResponseDto.java |
페이지 응답 신규 | B |
src/main/java/com/msg/fillmap/video/support/RegionExploreCursor.java |
사용자와 정렬 경계를 담는 커서 신규 | B |
src/test/java/com/msg/fillmap/video/** |
HTTP, 서비스, 커서, 쿼리 회귀 테스트 | B |
src/main/java/com/msg/fillmap/usergrid/** |
잘못 추가된 개인 도감 페이지 계약 제거와 기존 동작 복원 | B |
8. 미해결 질문¶
없음. 대상 화면, 페이지 크기, 개인화 기준, 폴백 정렬과 커서 방식은 사용자 정정으로 확정됐다.
[^1]: 불투명 커서: 클라이언트가 내부 값을 해석하지 않고 서버가 준 문자열을 다음 요청에 그대로 보내는 토큰이다.