OPEN HYPER STEP
← 목록으로 (master-project)
MASTER-PROJECT · 31 / 50
master-project
CHAPTER 31 / 50
읽기 약 2
FUNCTION

검색 API: 전문 검색 + 필터링


핵심 개념

PostgreSQL FTS·tsvector·GIN 인덱스·trigram·하이라이트 — 빠르고 정확한 검색 구현.

본문

tsvector 컬럼 추가

SQL📋 코드 (12줄)
-- 마이그레이션
alter table generations
add column search_vec tsvector
generated always as (
  to_tsvector('simple',
    coalesce(prompt, '') || ' ' ||
    coalesce(result, '')
  )
) stored;

-- GIN 인덱스 (검색 빠름)
create index idx_gens_fts on generations using gin(search_vec);

검색 쿼리

SQL📋 코드 (11줄)
-- to_tsquery (정확한 매치)
select * from generations
where search_vec @@ to_tsquery('simple', '카피');

-- websearch_to_tsquery (사용자 친화적)
select * from generations
where search_vec @@ websearch_to_tsquery('simple', 'AI 카피 OR 광고');

-- 부분 매치 (prefix)
select * from generations
where search_vec @@ to_tsquery('simple', '카피:*');

Supabase Client에서 사용

TS📋 코드 (6줄)
const { data } = await supabase
  .from('generations')
  .select('*')
  .textSearch('search_vec', `${query}:*`, { type: 'websearch' })
  .order('created_at', { ascending: false })
  .limit(20)

한국어 검색 (trigram)

SQL📋 코드 (11줄)
-- pg_trgm 확장 (PostgreSQL 부분 매치)
create extension if not exists pg_trgm;

-- 한국어는 형태소 분석기 부재 → trigram이 차선
create index idx_gens_prompt_trgm on generations using gin(prompt gin_trgm_ops);

-- 검색
select * from generations
where prompt % 'AI 카피'  -- 유사도 매치
order by similarity(prompt, 'AI 카피') desc
limit 20;

하이라이트

SQL📋 코드 (6줄)
select
  id,
  ts_headline('simple', prompt, to_tsquery('simple', 'AI'),
    'StartSel=<mark>, StopSel=</mark>') as highlighted_prompt
from generations
where search_vec @@ to_tsquery('simple', 'AI');
TS📋 코드 (2줄)
// 클라이언트
<div dangerouslySetInnerHTML={{ __html: gen.highlighted_prompt }} />

멀티 필터 (검색 + 카테고리 + 날짜)

TS📋 코드 (26줄)
// /api/generations/search
import { z } from 'zod'

const schema = z.object({
  q: z.string().optional(),
  category: z.enum(['blog', 'sns', 'email']).optional(),
  date_from: z.string().optional(),
  date_to: z.string().optional(),
  cursor: z.string().optional(),
})

export async function GET(request: Request) {
  const params = schema.parse(Object.fromEntries(new URL(request.url).searchParams))
  const supabase = await createClient()

  let q = supabase.from('generations').select('*')
  if (params.q) q = q.textSearch('search_vec', `${params.q}:*`)
  if (params.category) q = q.eq('category', params.category)
  if (params.date_from) q = q.gte('created_at', params.date_from)
  if (params.date_to) q = q.lte('created_at', params.date_to)
  if (params.cursor) q = q.lt('created_at', params.cursor)
  q = q.order('created_at', { ascending: false }).limit(20)

  const { data } = await q
  return Response.json({ data })
}

외부 검색엔진 (큰 서비스)

📋 코드 (6줄)
[Algolia]   — UI 컴포넌트·인스턴트 검색·$1/1K req
[MeiliSearch] — 오픈소스 self-host·자동 typo 보정
[Elasticsearch] — 강력하지만 운영 부담
[Typesense] — Algolia 오픈소스 대안

→ 1만+ 사용자 도달 전: PostgreSQL FTS로 충분

검색 성능 측정

SQL📋 코드 (8줄)
EXPLAIN ANALYZE
SELECT * FROM generations
WHERE search_vec @@ to_tsquery('simple', '카피:*')
ORDER BY created_at DESC
LIMIT 20;

-- "Bitmap Heap Scan" + "Bitmap Index Scan on idx_gens_fts" → 빠름
-- "Seq Scan" → 인덱스 안 탐 → 점검

다음 챕터

CH.32 "웹훅: Stripe + 외부 서비스 연동".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 마스터 프로젝트의 검색 API 부분을 분석해서
실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

검색 API 관련 모범 사례·안티패턴 5개를
비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 검색 API
최적화 가능 위치와 리스크를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 1인 개발자 시장의
검색 API 트렌드와 차별화 포인트를 정리해줘.

⭐ 이것만 기억하세요
검색 API: 전문 검색 + 필터링 이 3가지만 확실히 잡으세요
1.PostgreSQL FTS = 무료·내장·1만 행까지 충분
2.한국어는 trigram 보조 + ILIKE
3.다음 챕터에서 웹훅 처리


공유하기
진행도 31 / 50