TL;DR
- 현재 구조: 백엔드가 viewport 매물 raw row 200~500개를 GeoJSON으로 통째 전송 → 클라 Kakao MarkerClusterer가 묶음.
- 🔴 치명 결함 2가지: ORDER BY 없는 비결정 truncate(밀집지 매물 조용히 누락) + 서버 클러스터링 전무.
- 2026 교차검증 결론: "Kakao 유지 + 서버 집계(ST_SnapToGrid/행정구역) + 줌별 렌더"가 최적. MVT/WebGL 전면전환은 수백만+ 규모에서만 정당화.
- 이 앱은 이미 행정구역 폴리곤 +
adm_cd2 인프라 보유 → 한국 표준(네이버 cortarNo/호갱노노)과 일치하는 하이브리드(저줌=행정구역, 고줌=격자, 최고줌=개별핀) 채택.
1. 현재 구조 전수분석
스택: Kakao Maps JS SDK v2(services+clusterer) + PostGIS(GIST/ST_MakeEnvelope/ST_Contains). 백엔드 Python(FastAPI+SQLAlchemy+alembic), 프론트 React+Vite.
2. 최적성 판정
3. 2026 최적방법 교차검증
결론: "Kakao 베이스맵 유지 + 서버 집계 + 줌별 렌더"가 세 출처(커뮤니티·한국 부동산·기법비교) 모두에서 정답으로 수렴.
커뮤니티 컨센서스
- 클라 클러스터링 한계 ~1만점. DOM 마커는 ~1,500개부터 패닝/줌 끊김.
- 진짜 병목은 계산이 아니라 전체 GeoJSON 페이로드(1M점=230MB). Supercluster CPU는 수십만도 sub-second지만 다운로드가 문제.
- 임계점: ~10K=클라 충분 / 10K~100K=하이브리드 / 100K+=서버 필수 / 수백만+=MVT 벡터타일+WebGL.
한국 부동산 사례
Kakao 공식: "클라 위임은 매우 비효율적", 서버 사전집계 권장. 클라 클러스터러는 1만개에서 7~8초로 붕괴. Kakao 유지 합리적 — 무료 30만/일(국내최대), 직방이 카카오 공식 상업 레퍼런스.
PostGIS 18 기법 비교
geohash 비채택(경계 prefix 단절 버그+거친 단계로 SnapToGrid 순수 열화). H3는 수십만+ 확장·격자균일성 필요시 승격. 출처: postgis.net, Crunchy/Paul Ramsey, CARTO tile aggregation, h3geo, ky-gis.
4. 확정 설계
4-1. 집계 단위 — 하이브리드(행정구역 + 격자)
이 앱은 이미 행정구역 폴리곤(sido/sigungu/emd)+adm_cd2 인프라 보유 → 한국 표준과 일치. 격자만 새로 도입하면 폴리곤과 따로 논다.
[클라] Kakao Map (베이스맵 유지)
└ 줌(z) + bbox 전송
[서버] GET /v1/public/properties/map/clusters?z=&bbox=&{filters}
├ 저줌(시도/구/동) → 행정구역 집계 버블 (materialized view 캐싱)
├ 고줌(동 이하) → ST_SnapToGrid(geom, grid(z)) GROUP BY → count + 대표좌표
└ 최고줌 → 개별 매물 핀
[응답] { clusters:[{lng,lat,count,repr_price}], points:[{id,price,...}] }
→ 수만~수십만 건도 화면당 수십~수백 셀만 전송
4-2. 백엔드
- 신규
GET /v1/public/properties/map/clusters — z 따라 행정구역/격자/개별 분기.
- 기존
_build_public_feed_filter_conditions(:350-458) 필터 빌더 공유.
- 스키마:
properties_map.py에 MapClusterFeature(type="cluster", count, repr_price) 추가.
- 마이그레이션: 저줌 집계 materialized view + 인덱스(
e1f2a3b4c5d6 패턴).
- 버그 동반 수정:
search_within_bbox에 결정적 ORDER BY(view_count DESC, id) 추가.
4-3. 프론트
useMapPins 클러스터 응답 기반 전환 + getMapPins에 signal(AbortController).
FeedMapView: 클러스터/개별점 CustomOverlay 렌더, 클러스터 클릭→줌인 or adm_cd2 조회.
- 클러스터 숫자에 서버
count 정확값 반영.
4-4. UI/UX
FeedMapMiniCard 死코드 결정(복원/제거).
- raw hex→토큰화,
hover:bg-status-failed→중립 hover 토큰.
- 바텀시트 drag-to-dismiss, 34px 클러스터→44px,
FilterChip에 aria-pressed.
5. 구현 로드맵 (WSJF)
6. 검증
- 백엔드 pytest: 수만 row 시드 후 클러스터 count 합 == 전체 count, 줌별 분기, GIST EXPLAIN 인덱스 적중.
- 프론트 vitest: 클러스터 파싱, AbortController 취소.
- E2E: 줌아웃→행정구역 버블, 줌인→개별핀, 클릭→해당영역 조회.