- kiwi-nlp 기반 한국어 형태소 분석기 추가 - 추천 검색어 API 구현 (/api/schedules/suggestions) - Prefix 매칭, Bi-gram 다음 단어 예측 - 초성 검색 지원, 영문→한글 자동 변환 (Inko) - 사용자 사전 추가 (멤버/그룹명, 프로그램명 등) - DB 테이블: suggestion_queries, suggestion_word_pairs, suggestion_chosung Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.7 KiB
6.7 KiB
추천 검색어 시스템 개선 계획서
1. 현재 시스템 분석
1.1 기존 동작 방식
검색어 입력 → 띄어쓰기 분리 → Unigram/Bi-gram 저장 → prefix 매칭으로 추천
1.2 기존 코드의 문제점
| 문제 | 설명 | 예시 |
|---|---|---|
| 띄어쓰기 기준 분리 | 조사/어미가 포함된 형태로 저장됨 | "콘서트가", "열립니다" |
| 무의미한 검색어 저장 | 검색 결과 없어도 저장됨 | 오타, 의미없는 문자열 |
| Redis 캐시 미활용 | prefix 검색은 매번 DB 조회 | 성능 저하 |
| 서브쿼리 반복 | MAX(count) 매 쿼리마다 실행 |
성능 저하 |
| 초성 검색 미지원 | "ㅍㅁㅅ"로 검색 불가 | 사용성 저하 |
1.3 기존 테이블 구조
-- Unigram (전체 검색어)
CREATE TABLE search_queries (
query VARCHAR(255) PRIMARY KEY,
count INT DEFAULT 1,
last_searched_at TIMESTAMP
);
-- Bi-gram (단어 쌍)
CREATE TABLE word_pairs (
word1 VARCHAR(100),
word2 VARCHAR(100),
count INT DEFAULT 1,
PRIMARY KEY (word1, word2)
);
2. 개선 목표
- 형태소 분석기 도입: 의미있는 단어(명사, 고유명사)만 추출
- 검색 결과 기반 필터링: 실제 검색 결과가 있는 검색어만 저장
- 캐시 성능 개선: Redis 활용 강화
- 초성 검색 지원: "ㅍㅁㅅ" → "프로미스나인"
- 코드 구조 개선: Fastify 플러그인 구조에 맞게 재작성
3. 개선 설계
3.1 형태소 분석기 도입 (koalanlp)
설치 요구사항:
- Java 8 이상 (Docker에 추가 필요)
npm install koalanlp
사용할 분석기:
- KMR (코모란): 속도 빠름, 정확도 양호
추출 대상 품사:
| 태그 | 설명 | 예시 |
|---|---|---|
| NNP | 고유명사 | 프로미스나인, 지원 |
| NNG | 일반명사 | 콘서트, 생일 |
| NNB | 의존명사 | (필요시) |
| SL | 외국어 | fromis_9 |
예시:
입력: "프로미스나인 콘서트가 열립니다"
기존: ["프로미스나인", "콘서트가", "열립니다"]
개선: ["프로미스나인", "콘서트"]
3.2 검색어 저장 로직 개선
검색 실행
↓
Meilisearch 검색 결과 확인
↓ (결과 있음)
형태소 분석으로 명사 추출
↓
Unigram 저장 (전체 검색어)
↓
Bi-gram 저장 (명사 쌍)
↓
Redis 캐시 업데이트
3.3 테이블 구조 개선
-- 검색어 테이블 (변경 없음)
CREATE TABLE search_queries (
id INT AUTO_INCREMENT PRIMARY KEY,
query VARCHAR(255) UNIQUE NOT NULL,
count INT DEFAULT 1,
has_results BOOLEAN DEFAULT TRUE, -- 추가: 검색 결과 유무
last_searched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_prefix (query(50)),
INDEX idx_count (count DESC)
);
-- 단어 쌍 테이블 (변경 없음)
CREATE TABLE word_pairs (
word1 VARCHAR(100) NOT NULL,
word2 VARCHAR(100) NOT NULL,
count INT DEFAULT 1,
PRIMARY KEY (word1, word2),
INDEX idx_word1 (word1, count DESC)
);
-- 추가: 초성 인덱스 테이블
CREATE TABLE chosung_index (
chosung VARCHAR(50) NOT NULL, -- 초성 (예: "ㅍㅁㅅㄴㅇ")
word VARCHAR(100) NOT NULL, -- 원본 단어 (예: "프로미스나인")
count INT DEFAULT 1,
PRIMARY KEY (chosung, word),
INDEX idx_chosung (chosung)
);
3.4 Redis 캐시 전략
| 키 패턴 | 용도 | TTL |
|---|---|---|
suggest:prefix:{query} |
prefix 검색 결과 캐시 | 1시간 |
suggest:bigram:{word} |
Bi-gram 다음 단어 | 24시간 |
suggest:popular |
인기 검색어 Top 100 | 10분 |
suggest:max_count |
최대 검색 횟수 (임계값용) | 1시간 |
3.5 초성 검색 구현
// 한글 → 초성 변환
function getChosung(text) {
const CHOSUNG = ['ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ'];
let result = '';
for (const char of text) {
const code = char.charCodeAt(0) - 0xAC00;
if (code >= 0 && code <= 11171) {
result += CHOSUNG[Math.floor(code / 588)];
}
}
return result;
}
// "프로미스나인" → "ㅍㄹㅁㅅㄴㅇ"
3.6 API 엔드포인트
GET /api/schedules/suggestions
?q=검색어
&limit=10
Response:
{
"suggestions": ["프로미스나인", "프로미스나인 콘서트", ...]
}
4. 파일 구조
backend/src/
├── services/
│ └── suggestions/
│ ├── index.js # 메인 서비스 (저장/조회)
│ ├── morpheme.js # 형태소 분석 (koalanlp)
│ ├── chosung.js # 초성 변환/검색
│ └── cache.js # Redis 캐시 관리
├── routes/
│ └── schedules/
│ └── suggestions.js # API 라우트
└── plugins/
└── koalanlp.js # koalanlp 초기화 플러그인
5. 구현 순서
Phase 1: 기본 마이그레이션
- Docker에 Java 설치 추가
- koalanlp 패키지 설치 및 초기화
- 기존 suggestions.js를 Fastify 구조로 마이그레이션
- 기본 API 동작 확인
Phase 2: 형태소 분석 적용
- morpheme.js 구현 (명사 추출)
- saveSearchQuery에 형태소 분석 적용
- 검색 결과 있을 때만 저장하도록 수정
Phase 3: 캐시 및 성능 개선
- Redis 캐시 로직 강화
- MAX(count) 캐싱
- 인기 검색어 캐시
Phase 4: 초성 검색
- chosung.js 구현
- chosung_index 테이블 생성
- 초성 검색 API 통합
Phase 5: 테스트 및 정리
- 기존 데이터 마이그레이션 (형태소 재분석)
- 성능 테스트
- 문서화
6. Docker 변경사항
# Dockerfile에 Java 추가
FROM node:20-alpine
# Java 설치 (koalanlp 필요)
RUN apk add --no-cache openjdk11-jre
# JAVA_HOME 설정
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk
ENV PATH="$JAVA_HOME/bin:$PATH"
7. 예상 효과
| 항목 | 기존 | 개선 후 |
|---|---|---|
| 단어 추출 정확도 | 낮음 (띄어쓰기 기준) | 높음 (형태소 기준) |
| 불필요한 데이터 | 많음 | 적음 (결과 있는 것만) |
| 검색 응답 속도 | 보통 | 빠름 (캐시 활용) |
| 초성 검색 | 불가 | 가능 |
| 사용자 경험 | 보통 | 향상 |
8. 리스크 및 대응
| 리스크 | 영향 | 대응 방안 |
|---|---|---|
| Java 설치로 이미지 크기 증가 | ~100MB | Alpine + JRE만 설치 |
| koalanlp 초기 로딩 시간 | 첫 요청 지연 | 서버 시작 시 미리 로드 |
| 형태소 분석 오류 | 단어 추출 실패 | fallback으로 기존 방식 유지 |