사용자가 관심 있는 지역의 실거래 내역을 검색하고, AI 분석을 통해 부동산 트렌드를 빠르게 파악할 수 있도록 도와주는 부동산 정보 서비스의 백엔드 레포입니다.
- 관심 지역·날짜로 실거래 내역을 빠르게 조회
- OpenAI 기반 AI 분석 결과를 캐싱하여 신속하게 제공
- JWT 기반 인증으로 세션 안정성·보안성 확보
| 구분 | 기술 |
|---|---|
| 프레임워크 | Spring Boot |
| 인증 | Spring Security + JWT |
| DB 매핑 | MyBatis |
| 캐싱 | Caffeine Cache (Spring Cache) |
| DB | MySQL |
| 외부 API | 공공데이터 API, OpenAI API |
| 협업 | Git (SSAFT GIT) |
- Access Token 만료 시에도 Refresh Token 으로 무중단 재인증 가능
- Refresh Token Rotation 적용
- 매 로그인·토큰 갱신 시 새로운 Refresh Token 발급ㆍ저장
- 탈취된 토큰의 재사용 방지
houseinfos와housedeals테이블에 복합 인덱스 적용- JOIN + WHERE + ORDER BY 조건을 모두 커버하도록 설계
- 62ms → 16ms 평균 쿼리 응답속도 단축
- 보조 인덱스로만 구성하고 PK는 그대로 유지
- JOIN 키는 항상 뒤에, 선택도 높은 컬럼은 앞쪽에 배치
CREATE INDEX idx_houseinfos_composite
ON ssafyhome.houseinfos (sgg_cd, umd_cd, apt_nm, apt_seq);CREATE INDEX idx_housedeals_composite
ON ssafyhome.housedeals (apt_seq, deal_year, deal_month, deal_amount);- Caffeine 기반 Look-aside 캐시 전략
- 동일 요청 시 DB·외부 API 호출 없이 JVM 메모리에서 즉시 응답
- 실거래 조회 응답 시간 94.4% 개선
- OpenAI API 응답 시간 99.8% 개선
@Cacheable(value = "houseDeals", key = "#params.toCacheKey()")
public List<HouseDeal> getDeals(SearchParams params) { … }- Spring Cache + Caffeine 설정
- Look-aside 패턴으로 구현
[Client 요청]
↓
[Spring Controller]
↓
[Service → MyBatis]
↓
[MySQL] ←───┐
↓ │ (JOIN + WHERE + ORDER BY)
[CPU/Disk] │
↓ │
[Service] ──┘
↓
[Caffeine Cache]
↓
[응답 지연 해소]
| 전략 | Before | After | 개선율 |
|---|---|---|---|
| DB 인덱싱 | 62 ms | 16 ms | 74%↓ |
| 실거래 조회 캐싱 | 311 ms | 15 ms | 94.4%↓ |
| 외부 AI 응답 캐싱 | 1250 ms | 2 ms | 99.8%↓ |
- 공공데이터 자동 갱신을 위한 배치 작업 도입
- 외부 API 장애에 대비한 서킷 브레이커 적용
- Thread Pool 및 TPS 제한으로 과부하 방지
- 화이트리스트/블랙리스트 접근 제어 로직 추가
- 백엔드 및 AI 연동: 김민준, 김세민
이번 관통 프로젝트를 통해 다양한 기술들을 실제로 적용해보며 익숙해질 수 있었고, 수업에서 배운 개념들이 어떻게 연결되고 활용되는지를 직접 체감할 수 있는 좋은 기회였습니다. 특히 Spring Security, Spring AI 같은 기술들을 실전에 도입해보며, 단순히 ‘사용해봤다’를 넘어서 그 흐름과 맥락을 파악하는 수준까지 나아갈 수 있었습니다. MyBatis를 통한 MySQL 연동도 이제는 어느 정도 손에 익었다고 느껴집니다.
그중에서도 가장 인상 깊었던 부분은 캐싱 설계였습니다. 실거래 데이터를 매번 DB에서 조회하고, 부동산 트렌드를 OpenAI API로 호출하는 구조가 비효율적이라는 점을 느끼며 본격적으로 캐싱 전략을 고민하게 되었습니다. Look-aside, Read-through 등의
캐시 읽기 전략, Write-back, Write-through 같은캐시 쓰기 전략등 다양한 방식이 존재한다는 걸 처음 알게 되었고, “어디에 어떤 방식으로 캐싱할 것인가”가 서비스의 특성과 목적에 따라 달라진다는 사실이 매우 흥미로웠습니다.강사님 피드백 덕분에 Redis 대신 JVM 애플리케이션 스코프에서 Caffeine을 사용하는 구조로 설계했고, 네트워크 비용 없이도 성능을 눈에 띄게 개선할 수 있었습니다. Spring Cache 인터페이스를 활용해 구현 자체는 어렵지 않았지만, 시간이 더 있었다면 직접 캐시 알고리즘이나 evict 정책을 구현해보는 것도 도전해보고 싶다는 생각이 들었습니다.
반면 가장 아쉬웠던 부분은 보안을 깊이 있게 다루지 못한 점이었습니다. JWT 기반 인증은 도입했지만, 제한된 시간 속에서 보안 설계 전반에 대해 충분히 고민하거나 실험해보진 못했습니다.
특히 사용자 입력에 대한 서버 단 유효성 검증, 직접 구현한 솔트 및 해시 처리, JWT 저장 위치 전략 설계, 이중 암호화, 세션 방식과의 비교 실험 등 보안의 다양한 측면을 시도해보지 못한 점이 아쉬움으로 남습니다.강사님께서 강조하셨던 것처럼, JWT는 만들기도 쉽고 탈취도 쉬운 구조이며, 솔트가 노출되면 전체 토큰이 위조될 수 있다는 점은 보안을 설계할 때 반드시 고려해야 할 핵심 요소였습니다. 단순히 토큰을 발급하고 검증하는 것을 넘어서, 위조나 탈취 가능성을 어떻게 사전에 차단할 것인지까지 생각해야 한다는 사실을 이번 경험을 통해 절감하게 되었습니다.
이 외에도,
lombok사용 시 유효성 검증이 무력화될 수 있다는 점, 그리고 유효성 검사는 Controller가 아닌 Service 계층에서 수행해야 확장성과 일관성을 유지할 수 있다는 점도 실제 개발 환경을 고려한 중요한 학습이었습니다. 이런 작은 선택 하나하나가 유지보수성과 아키텍처의 품질을 결정짓는다는 것을 체감했습니다.이번 프로젝트는 단순히 기술을 적용해보는 것을 넘어, "어떻게 설계할 것인가", 그리고 **"왜 이렇게 설계해야 하는가"**를 스스로 끊임없이 묻고 답을 찾아가는 과정이었습니다. 단순히 기술을 써본 것보다, 그 기술을 왜 지금 내 애플리케이션에 적용했는지 설명할 수 있는 개발자가 되어야 한다는 사실을 이번 관통을 통해 깊이 깨달았습니다.
보안, 성능, 설계까지 많은 고민과 시도, 그리고 시행착오가 녹아든 의미 있는 프로젝트였고, 개발자로서 한 단계 성장할 수 있었던 소중한 경험이었습니다.
이번 과정을 통해, 그동안은 서비스 기획에 따른 기능 구현에만 집중했다면, 이번에는 최소화된 기능을 바탕으로 심도 있게 성능 최적화와 내부 동작 원리를 탐구할 수 있는 좋은 계기가 되었습니다.
특히 외부 API 호출이 많은 상황에서 발생하는 응답 지연과 비용 문제, 그리고 외부 서버 불안정성을 애플리케이션 레벨에서 다양한 기술적 트릭(인덱싱, 로컬 캐싱, 토큰 회전 등)을 활용해 효과적으로 관리할 수 있다는 점을 직접 체험할 수 있었습니다.
앞으로도 기능뿐 아니라 “왜” 그리고 “어떻게”가 명확한 설계를 지속해 나가고 싶습니다.




