ADR — 줌아웃 클러스터링은 행정 단위 집계, H3 기각¶
요약
지도를 축소하면 색칠할 100m 칸이 수백~수천 개가 되어 점 반죽이 된다. 묶는 기준으로 행정 단위(동→구→시) 집계를 채택하고 H3(육각형 격자)를 기각했다 (MSG-356, 2026-08-10).
가른 것은 성능이 아니라 마커에 지역 이름을 붙일 수 있느냐다. H3 육각형은 행정 경계를 무시해 걸치는 셀이 생기고, 중심 동 이름을 붙이면 숫자가 거짓이 된다. FillMap은 "부전2동에 31칸"이 곧 정보인 서비스라 이름 없는 ● 31 마커로는 화면 요구를 못 채운다.
구현은 이미 저장된 행정동 코드를 자르는 GROUP BY 하나다. 10자리면 동, 앞 5자리면 구, 앞 2자리면 시. 새 테이블도 마이그레이션도 라이브러리도 없다.
이 노트로 답할 수 있는 질문¶
- 지도를 축소했을 때 격자를 무엇으로 묶나?
- H3를 왜 안 썼나? 성능 문제였나?
- 서버는 무엇을 내려주고 화면은 무엇을 하나?
- 단위(동·구·시)는 누가 정하나? 전환 축척은?
- 바다처럼 행정동이 없는 격자는 어떻게 세나?
- H3를 다시 검토할 조건은 무엇인가?
맥락¶
확대 상태에서는 개별 칸 조회(GET /api/grids)로 충분하지만, 부산 전체나 전국으로 축소하면 전송량만 크고 화면에서는 읽히지 않는다. 축소 구간은 묶음 마커로 간다는 방향은 2026-08-08 멘토 멘토링에서 확정됐고, 남은 질문은 "무엇을 기준으로 묶느냐"였다.
선택지¶
| 행정 단위 집계 (채택) | H3 (기각) | |
|---|---|---|
| 묶음 기준 | 행정동·시군구·시도 경계 | 지구를 균등 분할한 육각형 타일 |
| 마커 표시 | "부전2동 31" (이름 + 개수) | "● 31" (개수만, 이름 불가) |
| 축소 사다리 | 동 → 구 → 시 | 작은 육각형 → 큰 육각형 |
| 서버 재료 | 이미 저장된 grids.region_code |
라이브러리 의존성 + DB 확장 또는 앱단 계산 |
| 어울리는 데이터 | 지역 이름이 정보인 것(부동산, 도감) | 이름이 무의미한 대량 점(차량 위치, 사진 수백만 장) |
함께 검토하고 기각한 것: 격자 시프트(100m 칸 5개를 500m 정사각형으로 합쳐 세기). 격자선과 정렬되는 장점은 있지만 역시 이름이 없다. 동 마커가 화면에 너무 성기다는 실측이 나오면 집계 단위를 하나 추가하는 확장 지점으로만 남겼다.
결정¶
행정 단위 집계. H3 기각 근거 셋:
- 이름 마커 불가. 육각형이 두 동에 걸치면 중심이 속한 동 이름을 붙여도 그 안의 일부 칸은 다른 동 것이라 숫자가 거짓이 된다. 같은 동 이름 마커가 둘 뜨는 문제도 생긴다. 숫자를 맞추려면 결국 행정동으로 다시 묶어야 해서 H3 단계가 아무 일도 하지 않게 된다. "H3로 묶되 시·구·동으로 보여주기"는 성립하지 않는 조합이다.
- 비용이 역방향. 채택안은 기존 재료로 쿼리 하나인데, H3는 우버 h3 자바 의존성에 DB에서 묶으려면 postgres h3 확장(현재 postgis 이미지에 없음)까지 필요하다.
- 규모 미달. H3가 이기는 구간은 수만 점 이상의 이름 없는 점 뭉치인데, 우리 데이터는 사용자당 수백에서 수천 격자다.
구현 — 저장된 코드를 잘라서 GROUP BY¶
행정안전부 표준 행정동 코드는 체계 자체가 계층적이다. 1111053000에서 앞 2자리가 시도(서울), 앞 5자리가 시군구(종로구), 10자리 전체가 행정동(사직동)이다. 격자가 처음 생길 때 이 코드를 라벨로 저장해 두었으므로(ADR 격자 행정동 라벨 grids.region_code, MSG-167) 자르는 길이만 바꾸면 묶음 단위가 바뀐다.
- 엔드포인트:
GET /api/grids/aggregation?swLat&swLng&neLat&neLng&unit=DONG|SIGUNGU|SIDO(친구 도감은GET /api/friends/{userId}/grids/aggregation) - 계약:
GridQueryService.getOccupiedAggregatesInViewport(userId, bounds, unit)·RegionUnit(DONG 10/3/1.0 · SIGUNGU 5/2/4.0 · SIDO 2/1/10.0 — codePrefixLength/nameTokenIndex/maxSpanDeg) - 항목 필드:
regionCode(마커 식별 키 — 동 이름은 전국 중복 가능) ·name·lat·lng(묶음 격자 중심 평균) ·count - 오류: 4401 뷰포트 불량 · 4402 단위별 상한 초과 · 4405 단위 불량(신규) · 9424 친구 아님(기존 재사용)
- 겉면은 MSG-374로
{currentRegion, items}객체가 됐다(내 도감만, 친구 집계는 배열 유지) — 지도 홈 API 연동 가이드 FE 1.5절
전환 축척은 서버가 정하지 않는다. 단위를 파라미터로 받을 뿐이고, 동 250m~500m·구 1km~8km·시 16km~전체는 FE 시작값이다.
행정동이 없는 격자도 이름 없는 항목 하나로 포함한다. 어떤 단위로 세어도 합이 개별 격자 총수와 일치해야 프론트 합산이 어긋나지 않기 때문이다. 반대로 점령 격자가 0개인 동은 항목 자체가 오지 않아 0짜리 마커를 거를 필요가 없다.
역할 분담: 서버는 행정 단위로 1차 압축만 하고, 마커끼리 겹치는 2차 병합은 네이버 지도 SDK의 마커 클러스터링이 한다. 병합 마커의 숫자는 count를 그냥 더하면 정확하다.
근거 — 업계 선례 (2026-08-10 실물 확인)¶
- 다방 지도: 수도권 시야(줌 11)에서 "은평구 224"·"수원시 팔달구 1087" 시군구 마커, 두 단계 확대(줌 14)하면 "잠실동 74" 동 마커로 전환. 어느 줌에서도 육각형이나 이름 없는 숫자 마커는 없었다. 호갱노노·직방·네이버 부동산도 같은 사다리.
- 공개 구현기: velog @koomin1227 — 시군구 260개, 더 축소하면 도·광역시 2단으로 서버가 집계해 내려주고 프론트는 그리기만 하는 같은 구조.
- 카카오 데브톡 topic 54562: "대량이면 서버 클러스터링이 정석"이라는 답.
- 가르는 기준은 데이터 성격이다. 지역 이름이 정보인 서비스는 행정 사다리, 이름 없는 대량 점은 geohash·H3 계열.
재검토 트리거¶
Phase 2의 전체 공개 지도(모든 사용자 데이터 합산 히트맵)처럼 이름 없는 대량 밀도 표현이 생기면 H3가 1순위 후보다. 개인 도감(이름 마커)과 전역 히트맵(밀도 표현)은 요구가 달라서, 그때 H3를 쓰면 데이터 성격에 따라 도구를 골랐다는 일관된 결정이 된다.