PWM 서비스를 만들면서 종목 검색 API를 구현했다. 처음에는 단순 조회 API라고 생각했다. 하지만 자동완성 UI를 붙이는 순간 검색 API의 성격이 달라진다. 사용자는 검색창에 한 번 입력하지만, 서버는 여러 번 요청을 받는다.
삼
삼성
삼성전
삼성전자
입력이 바뀔 때마다 API를 호출하면 검색 한 번이 여러 요청으로 증폭된다.
종목 조회, 포트폴리오 편입, 관심 종목, 차트 조회, 지수 비교가 모두 검색에서 시작된다.
즉 종목 검색 API는 단순 부가 기능이 아니라 트래픽이 몰릴 수 있는 지점이다.
현재 구조
현재 검색은 PostgreSQL의 local master DB를 먼저 조회한다.
결과가 충분하면 바로 반환하고, 결과가 없으면 외부 Provider를 호출하는 구조를 고려하고 있다.
Local DB 검색
→ 결과 있으면 반환
→ 결과 없으면 KIS / Yahoo 같은 외부 Provider 호출
병목도 두 단계로 나뉜다. 첫 번째는 Local DB 검색 병목이다. 두 번째는 외부 Provider 호출 병목이다. 이번 글에서는 첫 번째 병목만 본다. 외부 Provider는 그다음 문제다.
문제 지점
현재 검색 쿼리는 대략 이런 형태다.
where is_active = true
and (
search_symbol = :symbol
or search_symbol like :symbol || '%'
or search_name_ko like '%' || :keyword || '%'
or search_name_en like '%' || :keyword || '%'
)
search_symbol = ? 는 괜찮다. search_symbol like 'AAP%'도문제는 이쪽이다.
search_name_ko like '%삼성%'
search_name_en like '%tesla%'
contains 검색이다. 일반적인 B-Tree Index는 이런 검색에 적합하지 않다. B-Tree는 정렬된 구조라서 시작 지점이 명확한 검색에 강하다.
예를 들어 like 'AAP%'는AAP로 시작하는 위치를 찾고 그 이후를 보면 된다. 하지만 like '% AAP%'는 앞부분이 고정되어 있지 않다.
어디에 AAP가 들어있는지 알 수 없기 때문에 많은 row를 직접 검사할 가능성이 높다. 자동완성과 결합되면 더 위험하다.
입력
→ API 호출
→ LIKE '%keyword%' 검색
→ 반복
개발 환경에서는 잘 안 보인다. 데이터도 적고 동시 요청도 거의 없기 때문이다. 하지만 데이터가 늘고 요청이 반복되면 Local DB가 첫 번째
병목이 될 수 있다.
실행 계획 확인
감으로 판단하면 안 된다. 일단 실행 계획을 봐야 한다. 예를 들어 다음 쿼리를 확인한다.
EXPLAIN ANALYZE
SELECT *
FROM stock_master
WHERE is_active = true
AND search_name_ko LIKE '%a%'
LIMIT 10;
여기서 봐야 할 것은 단순하다.
Seq Scan이 발생하는가?
Index Scan이 발생하는가?
실제 실행 시간은 얼마인가?
읽은 row는 얼마나 되는가?
contains 검색에서 Seq Scan이 발생하면 예상했던 문제다. PostgreSQL이 인덱스로 바로 좁히지 못하고 많은 데이터를 검사하고 있다는 의미다. 물론 데이터가 적으면 실행 시간 자체는 짧게 나올 수 있다. 하지만 중요한 건 현재 시간이 아니라 증가 방향이다. 데이터가 늘고 자동완성 요청이 반복되면 이 비용은 계속 커진다.
이번 시리즈의 방향
여기서 바로 Redis를 붙이지 않을 생각이다. Redis를 먼저 붙이면 DB 병목이 가려진다. 캐시는 느린 쿼리를 숨길 수는 있지만, 쿼리 자체를 개선하지는 못한다. 먼저 Local DB 검색 병목을 확인하고 해결한다.
진행 순서는 이렇게 잡았다.
1. 현재 검색 쿼리 실행 계획 확인
2. API 부하 테스트로 병목 확인
3. pg_trgm + GIN Index 적용
4. 실행 계획과 응답 시간 재측정
5. 더 높은 부하에서 다시 테스트
6. Redis Cache 적용
7. 반복 요청 감소 효과 측정
즉 순서는 이렇다.
DB 검색 최적화
→ API 부하 확인
→ Cache 적용
먼저 DB가 기본기를 갖춰야 한다. 그다음에 Redis로 반복 요청을 줄이는 게 맞다.
다음 글
다음 글에서는 실제 검색 쿼리의 실행 계획을 확인한다. 목표는 하나다. LIKE '% keyword%' 검색이 실제로 어떤 실행 계획을 타는지 확인하는 것. Seq Scan이 발생한다면 그 이유를 보고, 이후 pg_trgm + GIN Index로 어떻게 바뀌는지 비교할 예정이다.
'종목 검색 api' 카테고리의 다른 글
| [종목 검색 API] 5. Redis Sentinel과 CircuitBreaker 장애 대응 실험 (0) | 2026.05.26 |
|---|---|
| [종목 검색 API] 4. Redis 장애 격리 실험: Timeout과 CircuitBreaker로 충분했을까? (0) | 2026.05.23 |
| [종목 검색 API] 3. API 부하 테스트: debounce, Redis Cache, Redis 장애 (0) | 2026.05.22 |
| [종목 검색 API] 2. Local DB 검색 Baseline 실험 (0) | 2026.05.21 |