프로젝트 소개
- NewCodes는 기업 기술 블로그 큐레이팅 서비스
- Spring Boot + PostgreSQL 아키텍처
- 검색어 자동완성 기능 구현: Term 기반 추천, 자모 분리 검색, 초성 검색, 기업 페이지 추천
성능 문제 발견
- Term 테이블에 11만 개 데이터 축적
- API 응답 시간이 1000ㅡ 이상으로 증가
- 목표: 100ms 이내 응답
1차 시도: 인덱스 추가 (1000ms → 700ms)
-
varchar_pattern_ops를 사용한 LIKE 접두사 검색 최적화 인덱스 생성 -
CONCURRENTLY옵션으로 서비스 중단 없이 인덱스 생성 - term, decomposed_term, chosung 컬럼에 각각 인덱스 적용
2차 시도: LOWER 함수 인덱스 (700ms → 110ms)
-
LOWER()함수 사용으로 인한 풀스캔 문제 발견 - 함수 기반 인덱스(Functional Index) 생성
-
LOWER(컬럼명) varchar_pattern_ops형태로 인덱스 재구성
3차 시도: JOIN → EXISTS (110ms → 100ms)
- Corporation과 Article의 INNER JOIN이 성능 병목
- EXISTS 서브쿼리로 변경하여 스캔 범위 축소
- "데이터 존재 여부"만 확인하도록 최적화
4차 시도: 비정규화 & 커버링 인덱스 (100ms → 90ms)
-
total_frequency컬럼 추가로 집계 연산 제거 - GROUP BY, SUM 연산을 미리 계산된 값으로 대체
- 커버링 인덱스로 I/O 횟수 감소
-
INCLUDE절로 term과 total_frequency를 인덱스에 포함
5차 시도: JDBC Template (90ms → 80ms)
- JPA/Hibernate 오버헤드 제거
- JDBC Template으로 직접 쿼리 실행
- 단순 조회에서는 ORM 레이어 생략이 효과적
Nginx Rate Limiting 문제 해결
- 초기 설정: 1초에 2회 제한, burst 10
- 100ms 디바운싱으로 인한 요청 실패 발생
- 개선: 1초에 10회 허용, burst 20으로 변경
- 444 → 429 status code 변경
응답 데이터 크기 축소
- JSON 필드명 제거, 배열 기반 응답으로 변경
- 타입을 숫자로 구분 (0: Corporation, 1: Theme, 2: Term)
- 네트워크 전송 시간 감소
CompletableFuture 병렬 처리
- Corporation, Theme, Term 조회를 독립적으로 동시 실행
- 순차 실행 대비 최대 응답 시간만큼만 소요
- ExecutorService와 예외 처리 추가
최종 성과
- 초기 1000ms → 최종 80ms (개발 서버), 40ms (운영 서버)
- 약 90% 이상 성능 개선
주요 학습 내용
- 문제 정의와 방향성 설정의 중요성
- AI 활용과 개발자의 검수 균형
- 전체 아키텍처 관점의 설계 필요
- 인덱스 종류 선택: 단일/복합/커버링 인덱스
- 함수 사용 시 인덱스 무효화 주의
- JPA 내부 동작 이해
- EXPLAIN을 통한 쿼리 실행 계획 분석
향후 개선 방향
- Trie 자료구조 사용
- 자주 검색되는 용어 캐싱
- CDN 활용 (글로벌 서비스 시)