fix: 추천 검색어 동적 임계값 필터링 복원
- SQL GREATEST()로 동적 임계값 적용 - MAX(count) * 1% 또는 최소 10회 중 더 큰 값 사용 - Prefix, 초성, 인기 검색어 모두 필터링 적용 - 데이터가 적을 때도 오타 필터링 가능 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
108265b1fd
commit
72db9dcdc1
1 changed files with 19 additions and 13 deletions
|
|
@ -15,8 +15,8 @@ const inko = new Inko();
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
// 추천 검색어 최소 검색 횟수 비율 (최대 대비)
|
// 추천 검색어 최소 검색 횟수 비율 (최대 대비)
|
||||||
MIN_COUNT_RATIO: 0.01,
|
MIN_COUNT_RATIO: 0.01,
|
||||||
// 최소 임계값 (데이터 적을 때)
|
// 최소 임계값 (데이터 적을 때 오타 방지)
|
||||||
MIN_COUNT_FLOOR: 5,
|
MIN_COUNT_FLOOR: 10,
|
||||||
// Redis 키 prefix
|
// Redis 키 prefix
|
||||||
REDIS_PREFIX: 'suggest:',
|
REDIS_PREFIX: 'suggest:',
|
||||||
// 캐시 TTL (초)
|
// 캐시 TTL (초)
|
||||||
|
|
@ -24,7 +24,6 @@ const CONFIG = {
|
||||||
PREFIX: 3600, // prefix 검색: 1시간
|
PREFIX: 3600, // prefix 검색: 1시간
|
||||||
BIGRAM: 86400, // bi-gram: 24시간
|
BIGRAM: 86400, // bi-gram: 24시간
|
||||||
POPULAR: 600, // 인기 검색어: 10분
|
POPULAR: 600, // 인기 검색어: 10분
|
||||||
MAX_COUNT: 3600, // 최대 횟수: 1시간
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,6 +207,7 @@ export class SuggestionService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix 매칭
|
* Prefix 매칭
|
||||||
|
* - GREATEST()로 동적 임계값 적용: MAX(count) * 1% 또는 최소 10 중 더 큰 값
|
||||||
*/
|
*/
|
||||||
async getPrefixSuggestions(prefix, koreanPrefix, limit) {
|
async getPrefixSuggestions(prefix, koreanPrefix, limit) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -216,19 +216,21 @@ export class SuggestionService {
|
||||||
if (koreanPrefix) {
|
if (koreanPrefix) {
|
||||||
// 영어 + 한글 변환 둘 다 검색
|
// 영어 + 한글 변환 둘 다 검색
|
||||||
[rows] = await this.db.query(
|
[rows] = await this.db.query(
|
||||||
`SELECT query, count FROM suggestion_queries
|
`SELECT query FROM suggestion_queries
|
||||||
WHERE query LIKE ? OR query LIKE ?
|
WHERE (query LIKE ? OR query LIKE ?)
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_queries), ?)
|
||||||
ORDER BY count DESC, last_searched_at DESC
|
ORDER BY count DESC, last_searched_at DESC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
[`${prefix}%`, `${koreanPrefix}%`, limit]
|
[`${prefix}%`, `${koreanPrefix}%`, CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
[rows] = await this.db.query(
|
[rows] = await this.db.query(
|
||||||
`SELECT query, count FROM suggestion_queries
|
`SELECT query FROM suggestion_queries
|
||||||
WHERE query LIKE ?
|
WHERE query LIKE ?
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_queries), ?)
|
||||||
ORDER BY count DESC, last_searched_at DESC
|
ORDER BY count DESC, last_searched_at DESC
|
||||||
LIMIT ?`,
|
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) {
|
async getChosungSuggestions(chosung, limit) {
|
||||||
try {
|
try {
|
||||||
const [rows] = await this.db.query(
|
const [rows] = await this.db.query(
|
||||||
`SELECT word, count FROM suggestion_chosung
|
`SELECT word FROM suggestion_chosung
|
||||||
WHERE chosung LIKE ?
|
WHERE chosung LIKE ?
|
||||||
|
AND count >= GREATEST((SELECT MAX(count) * ? FROM suggestion_chosung), ?)
|
||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
[`${chosung}%`, limit]
|
[`${chosung}%`, CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
|
||||||
);
|
);
|
||||||
|
|
||||||
return rows.map(r => r.word);
|
return rows.map(r => r.word);
|
||||||
|
|
@ -261,6 +265,7 @@ export class SuggestionService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 인기 검색어 조회
|
* 인기 검색어 조회
|
||||||
|
* - GREATEST()로 동적 임계값 적용
|
||||||
*/
|
*/
|
||||||
async getPopularQueries(limit = 10) {
|
async getPopularQueries(limit = 10) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -272,12 +277,13 @@ export class SuggestionService {
|
||||||
return JSON.parse(cached);
|
return JSON.parse(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DB 조회
|
// DB 조회 (동적 임계값 이상만)
|
||||||
const [rows] = await this.db.query(
|
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
|
ORDER BY count DESC
|
||||||
LIMIT ?`,
|
LIMIT ?`,
|
||||||
[limit]
|
[CONFIG.MIN_COUNT_RATIO, CONFIG.MIN_COUNT_FLOOR, limit]
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = rows.map(r => r.query);
|
const result = rows.map(r => r.query);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue