fromis_9/backend/docs/suggestion-improvement-plan.md
caadiq c201de203e feat: 추천 검색어 시스템 구현 (kiwi-nlp 형태소 분석)
- 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>
2026-01-18 13:01:29 +09:00

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. 개선 목표

  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 테이블 구조 개선

-- 검색어 테이블 (변경 없음)
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: 기본 마이그레이션

  1. Docker에 Java 설치 추가
  2. koalanlp 패키지 설치 및 초기화
  3. 기존 suggestions.js를 Fastify 구조로 마이그레이션
  4. 기본 API 동작 확인

Phase 2: 형태소 분석 적용

  1. morpheme.js 구현 (명사 추출)
  2. saveSearchQuery에 형태소 분석 적용
  3. 검색 결과 있을 때만 저장하도록 수정

Phase 3: 캐시 및 성능 개선

  1. Redis 캐시 로직 강화
  2. MAX(count) 캐싱
  3. 인기 검색어 캐시

Phase 4: 초성 검색

  1. chosung.js 구현
  2. chosung_index 테이블 생성
  3. 초성 검색 API 통합

Phase 5: 테스트 및 정리

  1. 기존 데이터 마이그레이션 (형태소 재분석)
  2. 성능 테스트
  3. 문서화

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으로 기존 방식 유지