Skip to content

MSG-356 feat: 지도 축소 시 행정 단위 격자 집계 조회 추가 - #144

Merged
s13121312 merged 10 commits into
developfrom
feature/MSG-356-zoom-cluster
Aug 11, 2026
Merged

MSG-356 feat: 지도 축소 시 행정 단위 격자 집계 조회 추가#144
s13121312 merged 10 commits into
developfrom
feature/MSG-356-zoom-cluster

Conversation

@s13121312

@s13121312 s13121312 commented Aug 10, 2026

Copy link
Copy Markdown
Member

🎫 관련 티켓

  • MSG-356 (티켓 종결은 FE 합동 전환 기준 실측 후입니다. 스펙 작업 로그에 종결 게이트로 명시)

작업 내용

  • 지도를 축소했을 때 개별 100m 격자 대신 행정 단위(동/구/시)로 묶어 센 집계를 내려주는 조회 2종을 추가했습니다.
    • GET /api/grids/aggregation?swLat&swLng&neLat&neLng&unit=DONG|SIGUNGU|SIDO (내 도감)
    • GET /api/friends/{userId}/grids/aggregation (친구 도감, 관계 검증 후 위임)
  • 응답 항목은 묶음 키(regionCode), 이름, 마커 좌표, 격자 수입니다. 행정동 미판정 격자(격자 중심이 바다에 떨어진 경우 등)는 이름 없는 항목 하나로 포함해, 어느 단위로 세어도 합이 개별 조회 총수와 일치합니다 (프론트가 마커를 더 병합해도 count 합산이 어긋나지 않음).
  • 계약 인터페이스 GridQueryService에 4번째 메서드 getOccupiedAggregatesInViewport(userId, bounds, unit)를 추가했습니다 (기존 3메서드 불변). 동반 신설: RegionUnit(단위별 코드 접두 길이/이름 토큰/뷰포트 상한 보유), RegionAggregateView, RegionAggregateResponseDto, 에러코드 4405 INVALID_AGGREGATION_UNIT.
  • 좌표 검증을 공통 지점으로 승격했습니다. WGS841 범위(위도 ±90, 경도 ±180) 검증이 뒤집힘·상한 검사보다 앞서고 NaN·무한대도 함께 걸러집니다. 기존 개별·페이지 조회 경로에도 같이 적용됩니다.
  • DB 마이그레이션 없음 — 기존 grids.region_code(V5), grids.center_geom(V1), regions.region_name 재료만 씁니다.
  • 테스트 27건 신규 (집계 통합 14, grid 컨트롤러 6, friend 7). 전체 빌드 1,323건 green. 변경 규모 20파일 +1,559/-3 (PRD·스펙·작업 로그 포함).
  • 문서: PRD·스펙, ADR(행정 단위 채택 및 H3 기각 — 컨플루언스), FE 연동 가이드 1.5절 확정 갱신.

🤔 고민한 내용

  • 묶음 단위: 행정 단위 vs H32/geohash 격자 묶음. 도감은 "어느 동네에 몇 칸"이 곧 정보라 이름 마커("부전2동 31")가 화면 요구인데, 육각형 타일에는 행정 이름을 붙일 수 없습니다(경계에 걸친 타일에 이름을 붙이면 숫자가 거짓이 됨). 다방·직방 등 국내 부동산 지도들의 표준 패턴이 행정 사다리인 것도 실물로 확인했습니다. 격자에 이미 저장된 행정동 코드3의 계층성(앞 2자리 시도, 앞 5자리 시군구) 덕에 매핑 테이블 없이 성립합니다.

  • 핵심 쿼리 (GridRepository.aggregateOccupiedInRange, native 원문):

    SELECT
        substring(g.region_code FROM 1 FOR :codeLen)     AS "regionCode",
        MIN(split_part(r.region_name, ' ', :nameToken))  AS "name",
        AVG(ST_Y(g.center_geom::geometry))               AS "lat",
        AVG(ST_X(g.center_geom::geometry))               AS "lng",
        COUNT(*)                                         AS "count"
    FROM user_grids ug
    JOIN grids g ON g.grid_id = ug.grid_id
    LEFT JOIN regions r ON r.region_code = g.region_code
    WHERE ug.user_id = :userId
        AND g.grid_y BETWEEN :minY AND :maxY
        AND g.grid_x BETWEEN :minX AND :maxX
    GROUP BY 1
    ORDER BY 1 NULLS LAST

    기존 개별 조회와 FROM·WHERE 술어가 글자 그대로 같아 합산 정합이 구조로 보장됩니다. GROUP BY가 식이 아니라 출력 컬럼 번호(1)인 이유: 같은 substring 식을 두 자리에 쓰면 :codeLen 바인딩이 서로 다른 파라미터 자리($1, $6)가 되어 PostgreSQL이 "must appear in the GROUP BY clause"로 거절합니다(실측). regions가 LEFT JOIN인 것도 의도입니다 — INNER면 미판정 격자가 총합에서 사라집니다.

  • 성능: 유계 논증(스캔 행이 사용자 점령 격자 수로 제한)에 실측을 더했습니다. 사용자 1인 1만 행 시드, 전국 시야 시도 집계 EXPLAIN ANALYZE4 실행 10.9ms로 기존 뷰포트 SLO(p95 300ms) 대비 27배 여유입니다. 재실측은 GRID_BENCHMARK=true로 벤치 테스트를 실행하면 됩니다.

  • 시도 단위 상한을 무상한 대신 10도로: 상한이 없으면 NaN이 모든 비교에서 false라 검증이 fail-open5 됩니다. 마커 대표 좌표는 지역 경계 중심이 아니라 그룹 점령 격자 중심의 평균입니다 — 부산 전체에서 해운대만 채운 사용자의 시 마커가 바다(도시 기하 중심)가 아니라 점령 지점 근처에 서고, 경계 도형이 없는 미판정 버킷에도 같은 규칙이 성립합니다.

👀 리뷰 포인트

  • 좌표 검증 공통화의 소급 범위: validateBounds 공통화로 기존 개별·페이지 조회도 NaN 같은 미정의 입력을 빈 결과 대신 4401로 명시 거절하게 됩니다. 정상 입력 계약은 불변임을 기존 회귀 테스트(MSG-73/90 계열 전건 green)로 확인했지만, 소급이 부담스러우면 집계 경로 한정으로 좁힐 수 있습니다.
  • 친구 쪽은 검증 코드 0줄 원칙(MSG-187 D2)대로 requireFriend 후 위임 한 줄이고, 실패는 기존 9424 단일 응답 재사용입니다(신규 에러코드 없음). 이 형태 유지가 맞는지 봐 주세요.
  • 데이터 엣지 하나: grids에 라벨은 있는데 regions 행이 없는 고아 코드면 regionCode는 있고 name만 null인 항목이 나올 수 있습니다. 현 시딩 체계에선 발생하지 않아 코드로 막지 않고 작업 로그에 기록만 했습니다.

Footnotes

  1. WGS84: GPS가 쓰는 세계 표준 위경도 좌표계. 위도 -9090, 경도 -180180이 정의역이다.

  2. H3: 우버가 만든 육각형 격자 인덱스. 지구를 크기별 육각형 타일로 나눠 위치 데이터를 묶는 데 쓴다.

  3. 행정동 코드: 행정안전부의 10자리 행정동 식별 코드. 앞자리가 상위 행정 단위를 가리켜 코드만 잘라도 시도/시군구로 묶을 수 있다.

  4. EXPLAIN ANALYZE: PostgreSQL이 쿼리를 실제로 실행하면서 실행 계획과 단계별 소요 시간을 보여주는 명령.

  5. fail-open: 검증이 이상 입력을 잡지 못하고 통과시키는 방향으로 뚫리는 것. NaN은 모든 비교가 거짓이라 "상한보다 크지 않다" 검사를 전부 통과한다.

Codex 커밋 리뷰 반영. 시도 무상한은 NaN·무한대 fail-open이라 10도 유한 상한+유한성 검증(4401 선행)으로 교체.
required=false 근거를 실제 핸들러 동작(누락은 이미 400, 문제는 4405와의 비대칭)으로 정정. 성능은 유계 논증
단정 대신 EXPLAIN(ANALYZE)+1만 행 시드 실측을 완료 조건으로 명시.
동/구/시 단위로 행정동 코드 접두를 잘라 GROUP BY 집계, 좌표 검증(WGS84 범위, NaN·무한대 포함)을
공통 지점으로 승격. 1만 행 시드 전국 시도 집계 EXPLAIN ANALYZE 실행 10.9ms (SLO 300ms 대비 27배 여유).
@claude

claude Bot commented Aug 10, 2026

Copy link
Copy Markdown

Claude finished @s13121312's task in 5m 8s —— View job


리뷰 완료

  • 컨텍스트 수집 (diff, 스펙 문서, 변경 파일)
  • 병렬 브랜치 경합 확인 (마이그레이션 번호, developCode 대역, Owner A/B 계약)
  • 스펙 대비 구현 대조 (docs/spec/MSG-356.md)
  • 실행 시 드러나는 결함 확인 (트랜잭션, 동시성, 시간대, 인가/소유 검증, N+1)
  • PR 본문 주장 검증 (무변경/멱등 등)
  • 리뷰 결과 정리 및 게시

확인 범위: GridQueryService/GridRepository/GridController/FriendController/FriendServiceImpl 실 diff(PR 커밋 범위로 격리 대조), GridQueryServiceImpl.validateBounds 소급 변경, native 집계 쿼리와 기존 findOccupiedInRange의 FROM/WHERE 술어 일치 여부, docs/spec/MSG-356.md 전체와 테스트 시나리오 27건 매핑, GridErrorCode/response-pattern.md developCode 대역, db/migration 최신 파일 목록.

발견

  • P3 · (브랜치 상태) — 이 브랜치의 db/migration은 V28까지만 있고 origin/develop은 V29·V30(MSG-363, 오늘 병합)까지 있습니다. 이 PR은 마이그레이션을 추가하지 않아 번호 충돌은 없지만, develop 대비 다소 뒤처진 베이스입니다. 머지 전 최신 develop과 동기화를 권장합니다.

이 외에 P1/P2급 문제는 찾지 못했습니다. 근거:

  • GridQueryService 계약은 4번째 메서드만 추가되고 기존 3메서드·구현체(GridQueryServiceImpl 단일 구현) 시그니처는 불변입니다.
  • developCode 4405는 grid 도메인 기존 대역(4400~4404) 바로 다음 값으로, 대역표(response-pattern.md) 갱신 없이도 충돌이 없습니다.
  • 집계 쿼리(aggregateOccupiedInRange)는 findOccupiedInRange와 FROM·WHERE 술어가 글자 그대로 동일해 총합 정합(FR-3)이 구조적으로 성립하고, regionsLEFT JOIN이라 행정동 미판정 격자도 누락되지 않습니다(FR-7 통합 테스트로 확인).
  • validateBounds 공통화(WGS84 범위·NaN/무한대 검증이 뒤집힘·상한 검사보다 선행)가 기존 개별·페이지 조회 경로까지 소급됐다는 스펙의 명시적 승인 사항과 일치하고, 회귀 테스트로 뒷받침됩니다.
  • 친구 집계 경로는 requireFriend 판정 후 위임 1줄로, 기존 친구 뷰포트 조회와 동일한 배선·9424 단일 실패 패턴을 그대로 따릅니다.
  • PR 본문의 테스트 건수 주장(집계 통합 14 · grid 컨트롤러 6 · friend 7 = 27)은 @DisplayName/MSG-356 태그 기준으로 실제 코드와 일치합니다.

브랜치: feature/MSG-356-zoom-cluster

@github-actions

Copy link
Copy Markdown

테스트 커버리지

Overall Project 93.67% 🍏
Files changed 100% 🍏

File Coverage
RegionAggregateView.java 100% 🍏
GridController.java 100% 🍏
FriendController.java 100% 🍏
GridErrorCode.java 100% 🍏
RegionAggregateResponseDto.java 100% 🍏
RegionUnit.java 100% 🍏
GridQueryServiceImpl.java 100% 🍏
FriendServiceImpl.java 96.32% 🍏

@s13121312
s13121312 merged commit b9971c0 into develop Aug 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant