Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions prisma/seed/search-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* 검색 집계 이벤트 + 인기 검색어 스냅샷 시드(검색 진입 화면 검증용).
*
* - 이벤트는 시드 유저 소유(account_id)로만 만들어 resetSeedScope가 유저 기준으로 정리한다.
* - 스냅샷은 이벤트에서 파생되는 캐시라 시드마다 전량 재생성한다(직전 정각 + 현재 정각 2개,
* 순위 변동 UP/DOWN/SAME/NEW가 모두 보이도록 구성).
* - 스냅샷은 시드가 쓰는 두 정각(직전·현재)만 지우고 재생성한다 — 다른 시각대의
* 기존 스냅샷은 시드 데이터가 아니므로 보존한다(릴리즈 리뷰 반영).
* 순위 변동 UP/DOWN/SAME/NEW가 모두 보이도록 구성.
*/
import type { PrismaClient } from '@prisma/client';

Expand Down Expand Up @@ -63,7 +64,9 @@ export async function seedSearchEvents(
),
});

await prisma.searchKeywordRankSnapshot.deleteMany();
await prisma.searchKeywordRankSnapshot.deleteMany({
where: { ranked_at: { in: [previousAt, rankedAt] } },
});
Comment on lines +67 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clean up snapshots from earlier seed runs

When the seed is rerun after the clock advances by more than one hour, snapshots created by the previous run fall outside these two timestamps and are never removed. resetSeedScope removes the corresponding seeded search events, while the snapshot table has unlimited retention, so repeated seed runs accumulate orphaned synthetic ranking history and the seed is no longer idempotent across hour boundaries; track and delete prior seed-owned snapshots rather than limiting cleanup solely to the new run's timestamps.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

미반영(트레이드오프): 스냅샷 소유권 추적은 컬럼 신설이 필요한 dev 시드 과설계. 크론이 시드 이벤트를 집계해 만든 스냅샷도 동일하게 잔존하므로 완전한 멱등은 어차피 불가. popularSearchKeywords는 최신 스냅샷만 노출하므로 과거 정각의 잔존 스냅샷은 화면에 나타나지 않고, 두 정각 한정 삭제로 같은 시간대 재시드의 중복(uk 충돌)은 방지됨.

await prisma.searchKeywordRankSnapshot.createMany({
data: [
...PREVIOUS_RANKING.map((keyword, i) => ({
Expand Down
9 changes: 9 additions & 0 deletions src/common/utils/search-keyword.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ describe('search-keyword utils', () => {
});
});

it('길이는 코드 포인트 기준이다(이모지 200개는 허용, 201개는 거절)', () => {
expect(
normalizeSearchKeyword('😀'.repeat(SEARCH_KEYWORD_MAX_LENGTH)).ok,
).toBe(true);
expect(
normalizeSearchKeyword('😀'.repeat(SEARCH_KEYWORD_MAX_LENGTH + 1)),
).toEqual({ ok: false, reason: 'TOO_LONG' });
});

it('정규화 후 200자를 넘으면 TOO_LONG으로 거절한다', () => {
const raw = 'a'.repeat(SEARCH_KEYWORD_MAX_LENGTH + 1);
expect(normalizeSearchKeyword(raw)).toEqual({
Expand Down
4 changes: 3 additions & 1 deletion src/common/utils/search-keyword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export function normalizeSearchKeyword(
): NormalizeSearchKeywordResult {
const keyword = raw.trim().replace(/\s+/g, ' ');
if (keyword.length === 0) return { ok: false, reason: 'EMPTY' };
if (keyword.length > SEARCH_KEYWORD_MAX_LENGTH) {
// MySQL VarChar(200)은 문자(코드 포인트) 수 기준 — UTF-16 단위(.length)로 세면
// 서로게이트 쌍(이모지 등)이 2로 계산돼 저장 가능한 검색어를 거절한다(릴리즈 리뷰 반영)
if ([...keyword].length > SEARCH_KEYWORD_MAX_LENGTH) {
return { ok: false, reason: 'TOO_LONG' };
}
return { ok: true, keyword };
Expand Down
Loading