fix: 추천 검색어 동적 임계값 필터링 복원

- SQL GREATEST()로 동적 임계값 적용
- MAX(count) * 1% 또는 최소 10회 중 더 큰 값 사용
- Prefix, 초성, 인기 검색어 모두 필터링 적용
- 데이터가 적을 때도 오타 필터링 가능

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-19 09:44:02 +09:00
parent 108265b1fd
commit 72db9dcdc1

View file

@ -15,8 +15,8 @@ const inko = new Inko();
const CONFIG = {
// 추천 검색어 최소 검색 횟수 비율 (최대 대비)
MIN_COUNT_RATIO: 0.01,
// 최소 임계값 (데이터 적을 때)
MIN_COUNT_FLOOR: 5,
// 최소 임계값 (데이터 적을 때 오타 방지)
MIN_COUNT_FLOOR: 10,
// Redis 키 prefix
REDIS_PREFIX: 'suggest:',
// 캐시 TTL (초)
@ -24,7 +24,6 @@ const CONFIG = {
PREFIX: 3600, // prefix 검색: 1시간
BIGRAM: 86400, // bi-gram: 24시간
POPULAR: 600, // 인기 검색어: 10분
MAX_COUNT: 3600, // 최대 횟수: 1시간
},
};
@ -208,6 +207,7 @@ export class SuggestionService {
/**
* Prefix 매칭
* - GREATEST() 동적 임계값 적용: MAX(count) * 1% 또는 최소 10
*/
async getPrefixSuggestions(prefix, koreanPrefix, limit) {
try {
@ -216,19 +216,21 @@ export class SuggestionService {
if (koreanPrefix) {
// 영어 + 한글 변환 둘 다 검색
[rows] = await this.db.query(
`SELECT query, count FROM suggestion_queries
WHERE query LIKE ? OR query LIKE ?
`SELECT query FROM suggestion_queries
WHERE (query LIKE ? OR query LIKE ?)
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_queries), ?)
ORDER BY count DESC, last_searched_at DESC
LIMIT ?`,
[`${prefix}%`, `${koreanPrefix}%`, limit]
[`${prefix}%`, `${koreanPrefix}%`, CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
);
} else {
[rows] = await this.db.query(
`SELECT query, count FROM suggestion_queries
`SELECT query FROM suggestion_queries
WHERE query LIKE ?
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_queries), ?)
ORDER BY count DESC, last_searched_at DESC
LIMIT ?`,
[`${prefix}%`, limit]
[`${prefix}%`, CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
);
}
@ -241,15 +243,17 @@ export class SuggestionService {
/**
* 초성 검색
* - GREATEST() 동적 임계값 적용
*/
async getChosungSuggestions(chosung, limit) {
try {
const [rows] = await this.db.query(
`SELECT word, count FROM suggestion_chosung
`SELECT word FROM suggestion_chosung
WHERE chosung LIKE ?
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_chosung), ?)
ORDER BY count DESC
LIMIT ?`,
[`${chosung}%`, limit]
[`${chosung}%`, CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
);
return rows.map(r => r.word);
@ -261,6 +265,7 @@ export class SuggestionService {
/**
* 인기 검색어 조회
* - GREATEST() 동적 임계값 적용
*/
async getPopularQueries(limit = 10) {
try {
@ -272,12 +277,13 @@ export class SuggestionService {
return JSON.parse(cached);
}
// DB 조회
// DB 조회 (동적 임계값 이상만)
const [rows] = await this.db.query(
`SELECT query, count FROM suggestion_queries
`SELECT query FROM suggestion_queries
WHERE count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_queries), ?)
ORDER BY count DESC
LIMIT ?`,
[limit]
[CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
);
const result = rows.map(r => r.query);