fix(suggestions): 동적 임계값으로 추천 검색어 필터링
- 최대 검색 횟수의 1% 또는 최소 10회 중 더 큰 값 적용 - GREATEST(MAX(count) * 0.01, 10) 사용 - 데이터가 적을 때도 오타 필터링 가능 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
88f15a3ec1
commit
02fe9314e4
1 changed files with 27 additions and 36 deletions
|
|
@ -9,6 +9,12 @@ const inko = new Inko();
|
||||||
const SUGGESTION_PREFIX = "suggestions:";
|
const SUGGESTION_PREFIX = "suggestions:";
|
||||||
const CACHE_TTL = 86400; // 24시간
|
const CACHE_TTL = 86400; // 24시간
|
||||||
|
|
||||||
|
// 추천 검색어로 노출되기 위한 최소 비율 (최대 검색 횟수 대비)
|
||||||
|
// 예: 0.01 = 최대 검색 횟수의 1% 이상만 노출
|
||||||
|
const MIN_COUNT_RATIO = 0.01;
|
||||||
|
// 최소 임계값 (데이터가 적을 때 오타 방지)
|
||||||
|
const MIN_COUNT_FLOOR = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 영문만 포함된 검색어인지 확인
|
* 영문만 포함된 검색어인지 확인
|
||||||
*/
|
*/
|
||||||
|
|
@ -166,38 +172,21 @@ export async function getSuggestions(query, limit = 10) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 다음 단어 예측 (Bi-gram 기반)
|
* 다음 단어 예측 (Bi-gram 기반)
|
||||||
|
* 동적 임계값을 사용하므로 Redis 캐시를 사용하지 않음
|
||||||
*/
|
*/
|
||||||
async function getNextWordSuggestions(lastWord, prefix, limit) {
|
async function getNextWordSuggestions(lastWord, prefix, limit) {
|
||||||
try {
|
try {
|
||||||
// 1. Redis 캐시 확인
|
const [rows] = await pool.query(
|
||||||
const cacheKey = `${SUGGESTION_PREFIX}${lastWord}`;
|
`SELECT word2, count FROM word_pairs
|
||||||
let nextWords = await redis.zrevrange(cacheKey, 0, limit - 1);
|
WHERE word1 = ?
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM word_pairs), ?)
|
||||||
|
ORDER BY count DESC
|
||||||
|
LIMIT ?`,
|
||||||
|
[lastWord, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
|
||||||
|
);
|
||||||
|
|
||||||
// 2. 캐시 미스 시 DB 조회 후 Redis 채우기
|
// prefix + 다음 단어 조합으로 반환
|
||||||
if (nextWords.length === 0) {
|
return rows.map((r) => `${prefix} ${r.word2}`);
|
||||||
const [rows] = await pool.query(
|
|
||||||
`SELECT word2, count FROM word_pairs
|
|
||||||
WHERE word1 = ?
|
|
||||||
ORDER BY count DESC
|
|
||||||
LIMIT ?`,
|
|
||||||
[lastWord, limit * 2] // 여유있게 가져오기
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length > 0) {
|
|
||||||
// Redis에 캐싱
|
|
||||||
const multi = redis.multi();
|
|
||||||
for (const row of rows) {
|
|
||||||
multi.zadd(cacheKey, row.count, row.word2);
|
|
||||||
}
|
|
||||||
multi.expire(cacheKey, CACHE_TTL);
|
|
||||||
await multi.exec();
|
|
||||||
|
|
||||||
nextWords = rows.map((r) => r.word2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. prefix + 다음 단어 조합으로 반환
|
|
||||||
return nextWords.slice(0, limit).map((word) => `${prefix} ${word}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[SearchSuggestion] Bi-gram 조회 오류:", error.message);
|
console.error("[SearchSuggestion] Bi-gram 조회 오류:", error.message);
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -218,19 +207,21 @@ async function getPrefixSuggestions(prefix, koreanPrefix, limit) {
|
||||||
// 영어 원본과 한글 변환 둘 다 검색
|
// 영어 원본과 한글 변환 둘 다 검색
|
||||||
[rows] = await pool.query(
|
[rows] = await pool.query(
|
||||||
`SELECT query FROM search_queries
|
`SELECT query FROM search_queries
|
||||||
WHERE query LIKE ? OR query LIKE ?
|
WHERE (query LIKE ? OR query LIKE ?)
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM search_queries), ?)
|
||||||
ORDER BY count DESC, last_searched_at DESC
|
ORDER BY count DESC, last_searched_at DESC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
[`${prefix}%`, `${koreanPrefix}%`, limit]
|
[`${prefix}%`, `${koreanPrefix}%`, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 단일 검색
|
// 단일 검색
|
||||||
[rows] = await pool.query(
|
[rows] = await pool.query(
|
||||||
`SELECT query FROM search_queries
|
`SELECT query FROM search_queries
|
||||||
WHERE query LIKE ?
|
WHERE query LIKE ?
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM search_queries), ?)
|
||||||
ORDER BY count DESC, last_searched_at DESC
|
ORDER BY count DESC, last_searched_at DESC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
[`${prefix}%`, limit]
|
[`${prefix}%`, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue