# 추천 검색어 시스템 개선 계획서 ## 1. 현재 시스템 분석 ### 1.1 기존 동작 방식 ``` 검색어 입력 → 띄어쓰기 분리 → Unigram/Bi-gram 저장 → prefix 매칭으로 추천 ``` ### 1.2 기존 코드의 문제점 | 문제 | 설명 | 예시 | |------|------|------| | **띄어쓰기 기준 분리** | 조사/어미가 포함된 형태로 저장됨 | "콘서트가", "열립니다" | | **무의미한 검색어 저장** | 검색 결과 없어도 저장됨 | 오타, 의미없는 문자열 | | **Redis 캐시 미활용** | prefix 검색은 매번 DB 조회 | 성능 저하 | | **서브쿼리 반복** | `MAX(count)` 매 쿼리마다 실행 | 성능 저하 | | **초성 검색 미지원** | "ㅍㅁㅅ"로 검색 불가 | 사용성 저하 | ### 1.3 기존 테이블 구조 ```sql -- 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. 개선 목표 1. **형태소 분석기 도입**: 의미있는 단어(명사, 고유명사)만 추출 2. **검색 결과 기반 필터링**: 실제 검색 결과가 있는 검색어만 저장 3. **캐시 성능 개선**: Redis 활용 강화 4. **초성 검색 지원**: "ㅍㅁㅅ" → "프로미스나인" 5. **코드 구조 개선**: 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 테이블 구조 개선 ```sql -- 검색어 테이블 (변경 없음) 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 초성 검색 구현 ```javascript // 한글 → 초성 변환 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: 기본 마이그레이션 1. [ ] Docker에 Java 설치 추가 2. [ ] koalanlp 패키지 설치 및 초기화 3. [ ] 기존 suggestions.js를 Fastify 구조로 마이그레이션 4. [ ] 기본 API 동작 확인 ### Phase 2: 형태소 분석 적용 5. [ ] morpheme.js 구현 (명사 추출) 6. [ ] saveSearchQuery에 형태소 분석 적용 7. [ ] 검색 결과 있을 때만 저장하도록 수정 ### Phase 3: 캐시 및 성능 개선 8. [ ] Redis 캐시 로직 강화 9. [ ] MAX(count) 캐싱 10. [ ] 인기 검색어 캐시 ### Phase 4: 초성 검색 11. [ ] chosung.js 구현 12. [ ] chosung_index 테이블 생성 13. [ ] 초성 검색 API 통합 ### Phase 5: 테스트 및 정리 14. [ ] 기존 데이터 마이그레이션 (형태소 재분석) 15. [ ] 성능 테스트 16. [ ] 문서화 --- ## 6. Docker 변경사항 ```dockerfile # 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으로 기존 방식 유지 |