# near_by_sold() API 성능 분석 및 개선 제안

## 1. 요약

| 구분 | 내용 |
|------|------|
| **적용 완료** | 캐시 조회 후 조기 반환 추가 (molit / all / sold 세 브랜치) |
| **원인** | 캐시를 저장만 하고 **조회 후 재사용하지 않음**, 동일 필터로 Mongo 2회 호출, MySQL 다수 쿼리·무제한 결과 |
| **효과** | 동일 조건 재요청 시 DB/모델 호출 없이 캐시 응답으로 체감 속도 대폭 개선 |

---

## 2. 적용한 수정 (Search.php)

- **molit**  
  `get_cache($cacheKey)` 후 `$cached`가 있으면 그대로 JSON 응답 후 `return`.
- **all**  
  동일하게 캐시 히트 시 조기 반환.
- **sold**  
  동일하게 캐시 히트 시 조기 반환.

캐시 키는 기존과 동일 (`near_by_molit_` / `near_by_all_` / `near_by_sold_` + `md5(json_encode($filter))`), TTL 86400초 유지.

---

## 3. 왜 느렸는지 (원인 정리)

### 3.1 캐시 미사용 (가장 큼)

- `$cached = get_cache($cacheKey)` 만 하고 **캐시 히트 시 재사용하지 않음**.
- 매 요청마다 Mongo + MySQL + PHP 후처리 전부 실행.
- **조치:** 위와 같이 캐시 히트 시 즉시 반환하도록 수정 완료.

### 3.2 Mongo Atlas Search 2회 호출 (all / sold)

- **getNearbySold($filter)** : 리스트용으로 동일 필터로 전체 검색.
- **getNearbySoldStatistics($filter)** : 통계용으로 **같은 필터**로 한 번 더 전체 검색.
- 동일한 `$search` compound 조건으로 aggregation을 두 번 돌리므로, 네트워크·Atlas 비용이 약 2배.
- **개선 방향:**  
  - 한 번의 aggregation에서 리스트 + 통계(월별 집계 등)를 함께 계산하거나,  
  - 리스트 결과를 한 번만 가져온 뒤 PHP에서 월별 통계를 계산하도록 변경 검토.

### 3.3 getNearbySold에 LIMIT 없음

- Mongo 파이프라인에 `$limit`이 없어, 조건에 맞는 **전부** 가져옴.
- 결과 건수가 많을수록 네트워크·메모리·PHP 후처리(거리 계산·정렬) 비용 증가.
- **개선 방향:**  
  - 화면/API에서 사용하는 최대 건수(예: 500~1000)를 정한 뒤 파이프라인 끝에 `$limit` 추가.

### 3.4 MySQL – getNearbyMolit 다수 쿼리

- **a_molit_danji** : `ST_Distance_Sphere`로 반경 내 단지 조회 (좌표 범위 + 거리).
- **a_molit_apt_sell_v2** / **a_molit_apt_rent_v2** : `aprpnHsmpCode IN (...)` + 기간 + molit_type.
- 단독/다가구·토지 사용 시: **a_addr_code** (또는 유사)로 bj_code 범위 조회 후 **a_molit_single_sell_v2**, **a_molit_land_sell** 등 추가 쿼리.
- **a_addr_code** : `bj_code IN (...)` 로 2depth/3depth 등 주소 보강용 재조회.
- **ST_Distance_Sphere** 사용 구간은 인덱스만으로는 한계가 있어, 범위 조건(`y BETWEEN ? AND ?`, `x BETWEEN ? AND ?`)이 인덱스로 타이트하게 걸리도록 하는 것이 중요.
- **개선 방향:**  
  - `a_molit_danji(y, x)`, `a_addr_code(center_lat, center_lng)` 등에 범위 조회용 복합 인덱스.  
  - 가능하면 면적 필터(area_min/max)를 DB 조건으로 넘겨 PHP 필터링 양 줄이기.

### 3.5 MySQL – getNearByGongmaeSold

- **g_gmul_seq** 기준으로 **g_gmul_addr**, **g_gmul_bid**, **g_gmul_basic**, **g_sold_price** 5테이블 JOIN.
- `g_gmul_addr.jibun_y`, `jibun_x` 범위 + `ST_Distance_Sphere` + `g_gmul_seq.PBCT_CLS_DTM >= ?`.
- 이미 `ORDER BY ... LIMIT 500` 이 있어, 그 전 단계에서 스캔 범위가 크면 비용이 큼.
- **개선 방향:**  
  - `g_gmul_addr(jibun_y, jibun_x)` 또는 공간 인덱스.  
  - `g_gmul_seq(complete_code, PBCT_CLS_DTM)` 등 조인·필터에 맞는 복합 인덱스 검토.

### 3.6 m_special_frame

- `m_special_code_frame` 단일 테이블 `SELECT *` 로 코드 프레임 조회.
- 쿼리 자체는 가벼우나, all/sold 브랜치에서 **매 요청마다** 호출 (캐시 히트 시에는 호출 안 함).
- **선택 개선:**  
  - 전역/요청 단위 메모리 캐시(예: 5분 TTL)로 감싸서 동일 요청 내 중복 또는 단기 반복 호출 감소.

### 3.7 make_list / make_special

- **make_list**: 전달된 배열을 루프하며 필드 매핑. DB 호출 없음.
- **make_special**: special 코드와 리스트 병합. DB 호출 없음.
- 둘 다 in-memory 처리라, **입력 리스트 크기**가 클수록 시간이 늘어남.  
  → Mongo/MySQL에서 가져오는 건수를 줄이면 여기 비용도 함께 감소.

---

## 4. 개선 제안 우선순위

| 순위 | 항목 | 예상 효과 | 난이도 |
|------|------|-----------|--------|
| 1 | **캐시 조기 반환** | 동일 조건 재요청 시 매우 큼 | ✅ 적용 완료 |
| 2 | Mongo **getNearbySold** 파이프라인에 **$limit** 추가 (예: 500~1000) | 결과 많을 때 체감 큼 | 낮음 |
| 3 | **리스트 + 통계** 한 번에 조회 (Mongo 1회로 통합 또는 PHP 집계) | Mongo 호출 2회 → 1회 | 중간 |
| 4 | **a_molit_danji**, **a_addr_code** 등 좌표/범위 쿼리 인덱스 점검·추가 | MySQL 구간 단축 | 중간 |
| 5 | **g_gmul_addr** / **g_gmul_seq** 인덱스 점검 | 공매 낙찰 쿼리 단축 | 중간 |
| 6 | m_special_frame 앱 캐시(짧은 TTL) | 소폭 | 낮음 |

---

## 5. 다음에 할 수 있는 작업

1. **Mongo getNearbySold**  
   - 파이프라인 마지막에 `['$limit' => 1000]` (또는 서비스에 맞는 상한) 추가.
2. **Mongo 리스트·통계 통합**  
   - `getNearbySoldStatistics`를 제거하고, `getNearbySold` 결과를 한 번만 가져온 뒤 PHP에서 월별 통계 계산하거나,  
   - 한 aggregation에서 `$facet` 등으로 리스트와 그룹 통계를 동시에 반환하도록 설계.
3. **MySQL**  
   - `a_molit_danji`, `a_addr_code`, `g_gmul_addr`, `g_gmul_seq` 등에 대해 위 조건에 맞는 인덱스 존재 여부 확인 및 추가.

이 문서는 `near_by_sold()` 컨트롤러와 연관 모델(Mongo/MySQL) 코드를 기준으로 작성했으며, 실제 체감 속도는 캐시 hit ratio와 데이터 양에 따라 달라집니다.
