---
id: db-pgvector-production
title: pgvector — 인덱스 / 차원 / 운영
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [database, pgvector, vector, vibe-coding]
tech_stack: { language: "SQL / Postgres", applicable_to: ["Backend"] }
applied_in: []
aliases: [pgvector, HNSW, ivfflat, vector index, Postgres vector, halfvec]
---

# pgvector — Production

> Postgres 안 vector 검색. **Postgres 가 이미 있으면 새 vector DB 안 필요**. HNSW 인덱스 (16+) 빠름. 차원 작게 + 양자화로 비용 절감.

## 📖 핵심 개념
- HNSW: 빠른 ANN — 정확도 + 속도 sweet spot.
- ivfflat: 메모리 작음 / 학습 필요.
- 거리: cosine / L2 / inner product.
- halfvec: 16-bit 차원 (절반 공간).

## 💻 코드 패턴

### 설정
```sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE docs (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(1536),  -- OpenAI text-embedding-3-small
  -- 또는 HALFVEC(1536) — 절반 메모리
  metadata JSONB
);
```

### HNSW 인덱스 (PG 16+, pgvector 0.5+)
```sql
CREATE INDEX docs_emb_hnsw ON docs USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);
```

빌드 시간: 1M rows ≈ 10-30분.

### Query
```sql
-- 가까운 5개
SELECT id, content, 1 - (embedding <=> $1::vector) AS similarity
FROM docs
ORDER BY embedding <=> $1::vector
LIMIT 5;
```

`<=>`: cosine distance, `<->`: L2, `<#>`: inner product.

### Hybrid (vector + filter)
```sql
SELECT id, content
FROM docs
WHERE metadata->>'lang' = 'ko'
  AND metadata->>'tenant_id' = $tenantId
ORDER BY embedding <=> $1::vector
LIMIT 5;
```

⚠️ Filter 가 강하면 인덱스 안 탐. partial 인덱스:
```sql
CREATE INDEX docs_ko_emb ON docs USING hnsw (embedding vector_cosine_ops)
  WHERE metadata->>'lang' = 'ko';
```

### 차원 압축
```sql
-- Matryoshka: 1536 → 256
ALTER TABLE docs ADD COLUMN embedding_short vector(256);
UPDATE docs SET embedding_short = embedding[1:256];
CREATE INDEX docs_short_hnsw ON docs USING hnsw (embedding_short vector_cosine_ops);
```

```ts
// OpenAI dimensions param
const r = await openai.embeddings.create({
  model: 'text-embedding-3-large',
  input,
  dimensions: 256,
});
```

### Quantization (halfvec)
```sql
-- 16-bit float — 절반 메모리, 90%+ 정확도
ALTER TABLE docs ADD COLUMN emb_half halfvec(1536);
UPDATE docs SET emb_half = embedding::halfvec;
CREATE INDEX docs_half_hnsw ON docs USING hnsw (emb_half halfvec_cosine_ops);
```

### Query 튜닝
```sql
-- 검색 정확도 vs 속도
SET hnsw.ef_search = 40;  -- default. 높을수록 정확 + 느림.

-- 결과 N 개 받고 rerank
SELECT * FROM docs ORDER BY embedding <=> $1 LIMIT 50;
-- → reranker 모델로 5개 선정
```

### 메모리 추정
```
1M rows × 1536 floats × 4 bytes = 6 GB raw
+ HNSW 인덱스 = 약 1.5x = 9 GB

halfvec: 1.5 GB raw + 인덱스 = 4 GB
dim 256 halfvec: 0.5 GB
```

### Batch insert (대량)
```sql
INSERT INTO docs (content, embedding) VALUES
  ('text 1', '[0.1, 0.2, ...]'::vector),
  ('text 2', '[0.3, 0.4, ...]'::vector);
```

```ts
// COPY (가장 빠름)
const stream = client.query(copyFrom('COPY docs (content, embedding) FROM STDIN'));
for (const r of rows) {
  stream.write(`${r.content}\t[${r.emb.join(',')}]\n`);
}
```

### Index 빌드 후 INSERT
```sql
-- 큰 데이터: 먼저 INSERT 다 한 후 index — 빠름
DROP INDEX IF EXISTS docs_emb_hnsw;
INSERT INTO docs ...;
CREATE INDEX docs_emb_hnsw ON docs USING hnsw (embedding vector_cosine_ops);
```

### Update 시 index 비싸짐
```
HNSW 인덱스 → INSERT/UPDATE 가 인덱스 reorganize.
대량 update 가 자주 = 속도 영향.
```

## 🤔 의사결정 기준
| 규모 | 추천 |
|---|---|
| <1M chunks | pgvector + HNSW |
| 1M-10M | pgvector + HNSW + halfvec |
| 10M-100M | pgvector partition + 또는 Qdrant |
| 100M+ | 전용 vector DB (Vespa, Milvus) |
| 단순 + Postgres 이미 사용 | pgvector |
| 새 분리 stack | Qdrant / Pinecone |

## ❌ 안티패턴
- **인덱스 없이 query**: 풀스캔 → 느림.
- **모든 dim 없이 max**: 비용. dim 압축.
- **Filter + sort 인덱스 못 활용**: partial 인덱스 또는 다단계.
- **Update 빈번 + HNSW**: 느림. periodic rebuild.
- **GIST 인덱스**: 옛 — HNSW 가 우월.
- **Cosine 가정 + 정규화 안 함**: dot 가 더 빠를 수 있음 (정규화 후).
- **여러 모델 같은 컬럼**: dim 또는 distribution 다름. 모델당 컬럼.

## 🤖 LLM 활용 힌트
- HNSW + halfvec + dim 압축 3종.
- Batch INSERT + 후 인덱스.
- ef_search 로 정확도/속도 trade.

## 🔗 관련 문서
- [[AI_RAG_Pattern_Basics]]
- [[AI_Embeddings_Comparison]]
- [[DB_Full_Text_Search]]
