TL;DR
- source : https://github.com/pgvector/pgvector
- ์ ๋ฌธ์ ์ธ vector DB๊ฐ ์ฌ๋ฌ ๊ฐ ์์ง๋ง postgreSQL์์ ์ฝ๊ฒ ์ธ ์ ์๋ค๋ ๊ฒ์ด ํฐ ๋ฉ๋ฆฌํธ
1. PGVector๋
PostgreSQL์ ๋ฒกํฐ๋ฅผ ์ง์ ์ ์ฅํด similarity search ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ ์คํ ์์ค ํ์ฅ ๋ชจ๋๋ก ๋ฌธ์์ ์๋ฏธ์ ์ ์ฌ๋ ๊ฒ์, ์ถ์ฒ ์์คํ
๋ฑ ๋ฒกํฐ ๊ธฐ๋ฐ AI ์์ฉ์ ์ ๋ฌธ ๋ฒกํฐ DB ์์ด๋ ๊ตฌํํ ์ ์๋ค.
๋ํ PostgreSQL์ ACID ํธ๋์ญ์
, ๋ณต์ , ๋ฐฑ์
, JOIN ๋ฑ์ ๋ชจ๋ ๊ธฐ๋ฅ๊ณผ ํตํฉ๋๋ฏ๋ก ์ผ๊ด์ฑ๊ณผ ๊ด๋ฆฌ ์ธก๋ฉด์์ ์ฅ์ ์ด ์๋ค.
2. ํน์ง
-
๋ค์ํ ๊ฑฐ๋ฆฌ ํจ์ ์ง์ : ๋ฒกํฐ ๊ฐ ์ ์ฌ๋๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํ ์ฌ๋ฌ ๊ฑฐ๋ฆฌ/์ ์ฌ๋ ์งํ๋ฅผ ๊ธฐ๋ณธ ์ ๊ณตํ๋ฉฐ, ์ฐ์ฐ์๋ก ๊ฐํธํ ์ฌ์ฉํ ์ ์์
- ์ ์ฌ๋ ์ข
๋ฅ : ๊ฐ์ด ์์์๋ก ๋ ๊ฐ๊น๋ค๊ณ ํ๋จ
- L2 distance
<->
- Inner product
<#>
: ๊ฐ์ด ์์์๋ก ๋ ๊ฐ๊น๊ฒ ํด์ํ๊ธฐ ์ํด ์์๋ฅผ ๋ถ์ฌ์ ๋ฐํ - Cosine distance
<=>
: 1-cosine similarity ๋ฅผ ๋ฐํ - L1 distance
<+>
- Hamming
<~>
- Jaccard
<\%>
- L2 distance
- ์ ์ฌ๋ ์ข
๋ฅ : ๊ฐ์ด ์์์๋ก ๋ ๊ฐ๊น๋ค๊ณ ํ๋จ
-
๋ฒกํฐ ์ ์ฉ ๋ฐ์ดํฐ ํ์ : vector(n) ํํ๋ก n์ฐจ์ ๋ฒกํฐ ํ์ ์ ์ ์ํ์ฌ ํ ์ด๋ธ ์ปฌ๋ผ์ ์ฌ์ฉํ ์ ์์
- ๋จ์ผ ์ ๋ฐ๋(float4) ๋ฒกํฐ ์ธ์๋ ์ ๋ฐ ์ ๋ฐ๋(float2)์ธ halfvec, ํฌ์๋ฒกํฐ sparsevec, ์ด์ง๋ฒกํฐ bit ๋ฑ์ ํ์ ์ ์ ๊ณต
- vector ํ์ ์ ์ต๋ 2,000์ฐจ์๊น์ง ์ง์(halfvec์ 4,000์ฐจ์)
- 2,000์ฐจ์์ ์ด๊ณผํ๋ ์๋ฒ ๋ฉ์ PCA ๋ฑ ์ฐจ์ ์ถ์๋ฅผ ํตํด ์ค์ด๊ฑฐ๋, ๋ถ๋์ดํ ๊ฒฝ์ฐ PGVector ๋์ ๋ฐฐ์ด ์ปฌ๋ผ(double precision[] ๋ฑ)์ ์ ์ฅํ ์๋ ์์ผ๋ ์ด๋ฌํ ๊ฒฝ์ฐ ์ธ๋ฑ์ค๋ฅผ ํ์ฉํ ๋น ๋ฅธ ๊ฒ์์ ์ ํ
-
์ ํํ ๊ฒ์๊ณผ ๊ทผ์ฌ ์ต๊ทผ์ ์ด์(ANN) ๊ฒ์ : ๊ธฐ๋ณธ์ ์ผ๋ก ์์ ํ์(์ ํํ ์ต๊ทผ์ ์ด์ ๊ฒ์)
- ๋ฐ๋ฉด ๋๋์ ๋ฒกํฐ์ ๋ํด ๋ ๋น ๋ฅธ ๊ฒ์์ด ํ์ํ๋ฉด ๊ทผ์ฌ ์ต๊ทผ์ ํ์(ANN) ์ ์ง์ํ๋ ํน์ ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ์ฌ ์๋๋ฅผ ๋์ผ ์ ์์
- HNSW(Hierarchical Navigable Small World)์ IVFFlat(Inverted File Index) ๋ ๊ฐ์ง ANN ์ธ๋ฑ์ค๋ฅผ ์ ๊ณต
- ์ผ๋ถ ์ ํ๋๋ฅผ ํฌ์ํ๊ณ ๋ ๊ฒ์ ์๋๋ฅผ ๋ํญ ํฅ์
-
SQL๊ณผ์ ์์ฐ์ค๋ฌ์ด ํตํฉ: PGVector๋ก ์ ์ฅํ ๋ฒกํฐ๋ ์ผ๋ฐ SQL ์ฟผ๋ฆฌ์์ ๋ค๋ฅธ ์ปฌ๋ผ๊ณผ ํจ๊ป ์ฌ์ฉ ๊ฐ๋ฅ
- ๋ฒกํฐ ์ ์ฌ๋ ์กฐ๊ฑด๊ณผ ๋ค๋ฅธ ์์ฑ์ ํํฐ ์กฐ๊ฑด์ ๊ฒฐํฉํ๊ฑฐ๋, JOIN์ ํตํด ๋ฉํ๋ฐ์ดํฐ์ ์๋ฒ ๋ฉ์ ํจ๊ป ํ์ฉํ๋ ๋ณต์กํ ์ง์๋ฅผ ์ํ
- ๋ฒกํฐ ๊ฒ์ ์ ์ฉ DB๋ฅผ ์ฌ์ฉํ ๋๋ณด๋ค ๊ฐ๋ฐ ์์ฐ์ฑ์ด ๋๊ณ , ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง๊ฐ ์ฉ์ด
- PostgreSQL ํด๋ผ์ด์ธํธ๋ฅผ ์ง์ํ๋ ๋ชจ๋ ์ธ์ด์์ ์ ๊ทผํ ์ ์์ด(C, Python, Java, Go ๋ฑ) ํ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ค์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฐ๊ณํ๊ธฐ ์ฌ์
-
ํ์ฅ์ฑ๊ณผ ๋์ฉ๋ ์ฒ๋ฆฌ : PGVector๋ ์ค์ธ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ์ ๋ง๊ฒ ์๋ฐฑ๋ง ๊ฐ ์ด์์ ๋ฒกํฐ๋ ๊ด๋ฆฌํ ์ ์๋๋ก ์ค๊ณ
- PostgreSQL ์์ฒด์ ํผํผํ ์ค์ผ์ผ ์
/์์ ๊ธฐ๋ฅ(ํํฐ์
๋, ๋ณ๋ ฌ ์ฟผ๋ฆฌ, ๋ณต์ ๋ฑ)์ ํจ๊ป ํ์ฉํ๋ฉด ๋ฒกํฐ ๋ฐ์ดํฐ๋์ด ์ฆ๊ฐํด๋ ์์ ์ ์ผ๋ก ์ฑ๋ฅ์ ์ ์ง
- ์๋ฅผ ๋ค์ด, ์์ธ ์๋ ๊ฒฝ์ฐ์๋ PostgreSQL์ parallel sequential scan1์ ํตํด ๋ค์ ์ฝ์ด๋ก ์ ์ฌ๋ ์ฐ์ฐ์ ๋ณ๋ ฌํ
- ์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋ ํ ์ธ๋ฑ์ฑ์ด๋ ํํฐ์ ๋๋๊ธฐ๋ฅผ ํตํด ๋๊ท๋ชจ ๋ฒกํฐ์ ์ ๋ค๋ฃฐ ์ ์์
- PostgreSQL์ WAL ๋ก๊ทธ2๋ฅผ ํตํ PITR3, ์คํธ๋ฆฌ๋ฐ ๋ณต์ 4๋ ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅํ๋ฏ๋ก ๋ฒกํฐ ๋ฐ์ดํฐ ๋ํ ๊ณ ๊ฐ์ฉ์ฑ์ ํ๋ณดํ ์ ์์
- PostgreSQL ์์ฒด์ ํผํผํ ์ค์ผ์ผ ์
/์์ ๊ธฐ๋ฅ(ํํฐ์
๋, ๋ณ๋ ฌ ์ฟผ๋ฆฌ, ๋ณต์ ๋ฑ)์ ํจ๊ป ํ์ฉํ๋ฉด ๋ฒกํฐ ๋ฐ์ดํฐ๋์ด ์ฆ๊ฐํด๋ ์์ ์ ์ผ๋ก ์ฑ๋ฅ์ ์ ์ง
3. ์ฅ๋จ์
์ฅ์
-
๊ธฐ์กด DB์์ ํตํฉ
- ๋ณ๋์ ๋ฒกํฐ ์ ์ฉ DB๋ฅผ ๊ตฌ์ถํ์ง ์๊ณ ๊ธฐ์กด PostgreSQL ํ๊ฒฝ์ ํตํฉํ์ฌ ์ฌ์ฉํ๋ฏ๋ก ์ด์ ๋ณต์ก๋๊ฐ ๋ฎ์
- ํธ๋์ญ์ , ๋ณด์, ๋ฐฑ์ ๋ฑ Postgres์ ์ฑ์ํ ๊ธฐ๋ฅ๋ค์ ๊ทธ๋๋ก ์ฌ์ฉํ ์ ์์
- ํ๋์ DB์์ ์ ๋ฌด ๋ฐ์ดํฐ์ ์๋ฒ ๋ฉ์ ํจ๊ป ๊ด๋ฆฌํ ์ ์์ด ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๋๊ณ , ํ์ค SQL๋ก ์ง์ ๊ฐ๋ฅ
- ํ์๋ค์ด ํ์ต ์ปค๋ธ ์์ด ํ์ฉ ๊ฐ๋ฅํ๋ฉฐ ๊ฐ๋ฐ ์์ฐ์ฑ์ด ๋์
-
๋ค์ํ ํ์ฉ๊ณผ ์ ํ์ฑ
- ๊ธฐ๋ณธ ์ค์ ์์ ์ ํํ ์ต๊ทผ์ ์ด์ ๊ฒ์(์์ ํ์)์ ์ ๊ณตํ๋ฏ๋ก ๊ฒฐ๊ณผ์ ์ ํ๋(์ฌํ์จ) ๋ฉด์์ ์ฐ์
- ํ์ํ ๊ฒฝ์ฐ ๋ฒกํฐ ์ ์ฌ๋ ์๊ณ๊ฐ์ ์ง์ ํ ๋ฒ์ ๊ฒ์๋ ๊ฐ๋ฅํ๋ฉฐ (WHERE embedding โ> โ๋ฒกํฐโ < ์๊ณ๊ฐ ํํ ), SUM/AVG ๊ฐ์ ์ง๊ณํจ์๋ ๋ฒกํฐ์ ์ ์ฉํ ์ ์์ด ํต๊ณ ๋ฒกํฐ ์ฐ์ถ์๋ ํ์ฉ ๊ฐ๋ฅ
- ์์ฉ ๋ถ์ผ๋ ๊ฒ์, ์ถ์ฒ, ์ด์ํ์ง ๋ฑ ๋ฒ์ฉ์ ์ด๋ฉฐ, ํ ์คํธ ์๋ฒ ๋ฉ๋ถํฐ ์ด๋ฏธ์ง ์๋ฒ ๋ฉ๊น์ง ํญ๋๊ฒ ์ง์
-
ํ์ฅ์ฑ & ์ฑ๋ฅ ์ ํ์ง
- ๋ฐ์ดํฐ ๊ท๋ชจ๋ ์๊ตฌ ์ฑ๋ฅ์ ๋ง์ถฐ ์ ์ฐํ๊ฒ ์ ํํ ์ ์์
- ์ธ๋ฑ์ค ์์ด๋ ์๋ง~์์ญ๋ง ๊ฑด ๊ท๋ชจ์์๋ ์ถฉ๋ถํ ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ , ๋ฐ์ดํฐ๊ฐ ๋์ด๋๋ฉด ๊ทผ์ฌ ์ต๊ทผ์ (ANN) ์ธ๋ฑ์ค๋ฅผ ์ถ๊ฐํ์ฌ ์ฑ๋ฅ์ ๋์ผ ์ ์์
- HNSW ๊ทธ๋ํ ์ธ๋ฑ์ค์ IVFFlat ์ธ๋ฑ์ค๋ฅผ ์ ๊ณตํ๋ฏ๋ก, ์ํฉ์ ๋ฐ๋ผ ์ ํฉํ ๋ฐฉ์์ ๊ณจ๋ผ ์๋์ ์ ํ๋์ ๊ท ํ์ ๋ง์ถ ์ ์์
- HNSW๋ ๋์ ์ ํ๋์ ๋น ๋ฅธ ์ง์๊ฐ ์ฅ์ ์ด๊ณ IVFFlat์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ด ์ ๊ณ ์ธ๋ฑ์ค ์์ฑ์ด ๋น ๋ฅธ ์ ์ด ์ฅ์
- ๋ณ๋ ฌ์ฒ๋ฆฌ, ํํฐ์ ๋ ๋ฑ PostgreSQL์ ์ฑ๋ฅ ๊ธฐ๋ฒ์ ํจ๊ป ํ์ฉํ๋ฉด ๋๋์ ๋ฒกํฐ๋ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์
-
์คํ ์์ค ๋ฐ ์ํ๊ณ
- PGVector๋ ํ๋ฐํ ๋ฐ์ ์ค์ธ ์คํ ์์ค ํ๋ก์ ํธ๋ก์ PostgreSQL ์ปค๋ฎค๋ํฐ์ ๋ค์ํ ๊ธฐ์ ๋ค์ ์ํด ๊ฐ์ ๋๊ณ ์์
- ๋ฌด๋ฃ๋ก ์ฌ์ฉํ ์ ์๊ณ , ๋ฒ์ ์ ์ ๋ฐ๋ผ ์ฑ๋ฅ ์ต์ ํ๋ ๊ธฐ๋ฅ ์ถ๊ฐ(HNSW ์ง์, ์ธ๋ฑ์ค ๋ณ๋ ฌ ๊ตฌ์ถ ๋ฑ)๋ ๋น ๋ฅด๊ฒ ์ด๋ค์ง๊ณ ์์
- LangChain ๊ฐ์ ์คํ์์ค ํ๋ ์์ํฌ์ ์ฝ๊ฒ ์ฐ๋๋์ด ๋ฐฑ์๋ ๋ฒกํฐ์คํ ์ด๋ก ํ์ฉํ ์ ์๊ณ , Supabase ๋ฑ์ ์๋น์ค์์ ๊ธฐ๋ณธ ์ง์ํ๋ ๋ฑ ์ํ๊ณ๊ฐ ํ์ฑ๋์ด ์์
๋จ์
-
์ ๋ฌธ ๋ฒกํฐ DB ๋๋น ์ฑ๋ฅ ํ๊ณ
- PGVector๋ ๋ฒ์ฉ DB ์์ ํ์ฅ์ธ ๋งํผ, ๋์ผ ํ๋์จ์ด์์ specialized ๋ฒกํฐ ์ ์ฉ DB (Faiss, Milvus ๋ฑ) ๋๋น ์ ๋ ์ฑ๋ฅ์ ๋ค์ ๋จ์ด์ง ์ ์์
- ๋ชจ๋ ์ฐ์ฐ์ด CPU ๊ธฐ๋ฐ์ผ๋ก ์ด๋ค์ง๋ฉฐ, ๋์ฉ๋ ๋ฒกํฐ์ ๋ํ ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ GPU ๊ฐ์ ๋ฑ์ด ์์ผ๋ฏ๋ก, ์์ฒ๋ง ๋จ์ ์ด์์ ๋ฒกํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฒ์ํด์ผ ํ๋ ์ด๋ํ ์ค์ผ์ผ์์๋ ์ฑ๋ฅ ๋ณ๋ชฉ์ด ์๊ธธ ์ ์์
- ์ธ๋ฑ์ค๋ฅผ ์ฐ์ง ์๋ ์ ํ ๊ฒ์์ ๊ฒฝ์ฐ ์ ํ ์๊ฐ์ด ๋ค๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ๋ง์ผ๋ฉด ์๋๊ฐ ๋๋ ค์ง๊ณ , ๊ทผ์ฌ ๊ฒ์๋ ๋งค๊ฐ๋ณ์ ํ๋์ ๋ฐ๋ผ ์๋๊ฐ ํฌ๊ฒ ๋ฌ๋ผ์ง
- ๋๊ท๋ชจ ์๋น์ค์์๋ ์ํฉ์ ๋ฐ๋ผ ๋ณ๋์ ๋ฒกํฐDB๋ฅผ ๊ณ ๋ คํ๊ฑฐ๋, PGVector ์ฌ์ฉ ์์๋ ์ถฉ๋ถํ ์ธ๋ฑ์ค ํ๋๊ณผ ์ค์ผ์ผ ์์(์ค๋ฉ/ํํฐ์ ๋) ์ ๋ต์ด ํ์
-
์ธ๋ฑ์ฑ ๊ด๋ จ ์ ์ฝ
- ANN ์ธ๋ฑ์ค๋ ์ฌ์ฉ์๊ฐ ๋ช
์์ ์ผ๋ก ์์ฑํด์ผ ํ๋ฉฐ, ์๋ชป ์ค์ ํ ๊ฒฝ์ฐ ๊ฒ์ ์ ํ๋๊ฐ ๋ฎ์์ง ์ ์์
- IVFFlat ์ธ๋ฑ์ค๋ ํด๋ฌ์คํฐ ๊ฐ์(lists) ๋ฅผ ์ ์ ํ ์ง์ ํ์ง ์์ผ๋ฉด ๋ฆฌ์ฝ ์ ํ๋ ์กฐํ ๋ถ์ ํ์ด ๋ฐ์ํ ์ ์๊ณ , ๋๋ฌด ์ด๊ธฐ ๋จ๊ณ์ ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค๋ฉด ๋ฐ์ดํฐ ๋ถํฌ๊ฐ ์น์ฐ์ณ ์ ๋๋ก ์๋ํ์ง ์์ ์๋ ์์
- IVFFlat์ ์ฟผ๋ฆฌ ์ ๊ฒ์ํ ๋ฆฌ์คํธ ์(probes) ๋ฅผ ๋์ฌ์ผ ์ ํ๋๊ฐ ์ฌ๋ผ๊ฐ๋๋ฐ, ๋๋ฌด ๋ฎ๊ฒ ์ก์ผ๋ฉด ๊ทผ์ฌ๋ก ์ธํด ์ผ๋ถ ๊ทผ์ ์ด์์ ๋์น ์ ์์
- HNSW ์ธ๋ฑ์ค๋ ๋งค๊ฐ๋ณ์ m, ef_search ๋ฑ์ ํตํด ์ ํ๋-์๋ ๊ท ํ์ ์ก์์ผ ํ๋ฉฐ, ๊ธฐ๋ณธ๊ฐ(ef_search=40)์ผ๋ก ํํฐ ์กฐ๊ฑด ์๋ ์ง์๋ฅผ ํ๋ฉด ๊ฒฐ๊ณผ ๋๋ฝ์ด ์๊ธธ ์ ์์ด ๋์ฌ์ผ ํ๋ ๋ฑ ํ๋ ํฌ์ธํธ๊ฐ ์กด์ฌ
- ์ฆ, PGVector์ ์ธ๋ฑ์ค๋ ์๋ ์ต์ ํ๋ณด๋ค๋ ์๋ ์กฐ์ ์ด ํ์ํ ๋ถ๋ถ์ด ์์ด ์ค๋ฌด ์ ์ฉ ์ ๋ฒค์น๋งํฌ์ ๋ชจ๋ํฐ๋ง์ ๊ฑฐ์ณ ๋งค๊ฐ๋ณ์๋ฅผ ๋ง์ถ๋ ๋ ธ๋ ฅ์ด ํ์
- ANN ์ธ๋ฑ์ค๋ ์ฌ์ฉ์๊ฐ ๋ช
์์ ์ผ๋ก ์์ฑํด์ผ ํ๋ฉฐ, ์๋ชป ์ค์ ํ ๊ฒฝ์ฐ ๊ฒ์ ์ ํ๋๊ฐ ๋ฎ์์ง ์ ์์
-
์ฐจ์ ๋ฐ ๋ฐ์ดํฐ ํ์ ํ๊ณ
- ์์ ์ธ๊ธํ๋ฏ์ด vector ํ์
์ ์ต๋ 2,000์ฐจ์๊น์ง๋ง ์ธ๋ฑ์ฑ์ด ์ง์
- ๋งค์ฐ ๊ณ ์ฐจ์(์: 10,000์ฐจ์ ์ด์) ์๋ฒ ๋ฉ์ ๊ฒฝ์ฐ ๋ฐ๋ก ์ธ๋ฑ์ค๋ฅผ ์์ฑํ ์ ์์ผ๋ฉฐ, ์ด๋ฐ ๊ฒฝ์ฐ ์ฐจ์์ ์ถ์ํ๊ฑฐ๋ ์ ๋ฐ์ ๋ฐ๋ ํ์ (halfvec, ์ต๋ 4,000์ฐจ์)์ ๊ณ ๋ ค
- ๋ํ ๋ฒกํฐ ์ปฌ๋ผ์ ์ ์ํ ๋ ์ฐจ์์ ๊ณ ์ ํด์ผ ํ๋ฏ๋ก, ๋ง์ฝ ๋ค๋ฅธ ์ฐจ์์ ์๋ฒ ๋ฉ์ ํจ๊ป ์ ์ฅํ ํ์๊ฐ ์๋ค๋ฉด ์ฌ๋ฌ ์ปฌ๋ผ์ผ๋ก ๊ตฌ๋ถํ๊ฑฐ๋ ๋ณ๋ ํ ์ด๋ธ๋ก ๊ด๋ฆฌ
- ํํธ PGVector์ ๋ฒกํฐ ํ์
์ ๋ถ๋์์(float4) ์ ๋ฐ๋๋ฅผ ๊ฐ์ง๋๋ฐ, ๋ณด๋ค ๋์ ์ ๋ฐ๋๊ฐ ํ์ํ ๊ฒฝ์ฐ (์: ๋งค์ฐ ๋ฏธ์ธํ ์ ์ฌ๋ ์ฐจ์ด๋ฅผ ๊ตฌ๋ถํด์ผ ํ ๋) ๊ธฐ๋ณธ ์ ๊ณต ํ์
์ผ๋ก๋ ์ด๋ ค์ธ ์ ์์
- ์ด๋๋ ์ฐจ์ ์ฑ ์ผ๋ก PostgreSQL์ DOUBLE PRECISION[] ๋ฐฐ์ด ๋ฑ์ ์๋ฒ ๋ฉ์ ์ ์ฅํ๊ณ ์ฌ์ฉ์ ์ ์ ํจ์๋ก ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ์ฐํด์ผ ํ๋, ์ด๋ฌํ ๋ฐฉ์์ PGVector์ ์ธ๋ฑ์ค๋ฅผ ํ์ฉํ์ง ๋ชปํด ์ฑ๋ฅ์ด ๊ฐ์
- ์ ๋ฐ๋์ ์ฑ๋ฅ ์ฌ์ด์ ํธ๋ ์ด๋์คํ๋ฅผ ๊ณ ๋ คํด์ผ ํจ
- ์์ ์ธ๊ธํ๋ฏ์ด vector ํ์
์ ์ต๋ 2,000์ฐจ์๊น์ง๋ง ์ธ๋ฑ์ฑ์ด ์ง์
-
์์ ์ฌ์ฉ ๋ฐ ๊ธฐํ
- HNSW ์ธ๋ฑ์ค๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง๊ณ ๊ตฌ์ถ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆด ์ ์์
- ์๋ฐฑ๋ง ๋ฒกํฐ์ ๋ํด HNSW ์์ฑ ์ ์์ญ GB์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ํ๊ณ , maintenance_work_mem ํ๋ผ๋ฏธํฐ๋ฅผ ๋์ฌ์ค์ผ ํจ
- ๋ฐ๋ฉด IVFFlat์ k-ํ๊ท ํ์ต์ ํด์ผ ํ๋ฏ๋ก ์ธ๋ฑ์ค ์์ฑ ์ CPU ๋ถํ๊ฐ ๋์ ์ ์์
- ์ธ๋ฑ์ค ํฌ๊ธฐ๋ ๋ฐ์ดํฐ์์ ๋น๋กํ์ฌ ๋์คํฌ๋ฅผ ์ฐจ์งํ๋ฏ๋ก ์ ์ฅ๊ณต๊ฐ ๊ณํ์ด ํ์
- PGVector๋ ๊ธฐ๋ณธ์ ์ผ๋ก PostgreSQL ์ฑ๊ธ ๋ ธ๋์ ์์กดํ๋ฏ๋ก ์ํ์ ํ์ฅ(์ค๋ฉ)์ ์ฌ์ฉ์๊ฐ ๊ตฌํํด์ผ ํจ
- ๋์ฉ๋ ๋ฐ์ดํฐ์ ์ ์ฌ๋ฌ ๋ ธ๋๋ก ๋ถ์ฐํ๋ ค๋ฉด Citus์ ๊ฐ์ ํ์ฅ์ด๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ ์ค๋ฉ์ ๊ฒํ ํด์ผ ํ๋ฉฐ, ์ด๋ฐ ๊ฒฝ์ฐ ๋ฒกํฐ ์ ์ฌ๋ ์ง์๋ฅผ ๋ถ์ฐ ์คํํ๋ ๋ก์ง์ด ์ถ๊ฐ๋ก ํ์ํ ์ ์์
- HNSW ์ธ๋ฑ์ค๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ง๊ณ ๊ตฌ์ถ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆด ์ ์์
4. ์ธ๋ฑ์ค : HNSW vs IVFFlat
TL;DR
- HNSW๋ ์ ํ๋ ๋๊ณ ๊ฒ์์ด ๋น ๋ฅด๋ ๋ฉ๋ชจ๋ฆฌ/๊ตฌ์ถ๋น์ฉ์ด ํผ
- IVFFlat์ ๊ฐ๋ฒผ์ด ๋์ ํ๋ ๋์ด๋๊ฐ ์๊ณ ์ ํ๋๊ฐ ์ฝ๊ฐ ๋ฎ์ ์ ์์
- ๋ ๋ค ๋งค๊ฐ๋ณ์ ์กฐ์ ์ผ๋ก ์ฑ๋ฅ์ ๊ฐ์ ํ ์ฌ์ง๊ฐ ํฌ๋ฏ๋ก, ๋ฐ์ดํฐ ๊ท๋ชจ์ ์๋ต ์๋ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ์ ํ ์ธ๋ฑ์ค๋ฅผ ์ ํํ๊ณ ์คํ์ ํตํด ์ต์ ์ ์ค์ ์ ์ฐพ์๋ด๋ ๊ฒ์ด ์ค์
PGVector๋ ๋ ๊ฐ์ง ์ข ๋ฅ์ ์ฃผ์ ๊ทผ์ฌ ๊ฒ์ ์ธ๋ฑ์ค๋ฅผ ์ ๊ณตํ๋ค
HNSW (Hierarchical Navigable Small World)
- Hierarchical Navigable Small World๋ฅผ ๋ค์ธต์ผ๋ก ๊ตฌ์ถํ์ฌ ๊ทผ์ ์ด์์ ์ฐพ๋ ๋ฐฉ์
- ๋์ ๊ฒ์ ์ ํ๋์ ๋ฎ์ ์ง์ฐ์ ์ ๊ณตํ๋ฉฐ, ์ฃผ์ด์ง ์์ ๋ด์์ IVFFlat๋ณด๋ค ์ ๋ฐ์ ์ผ๋ก ์ฐ์ํ ์๋-์ ํ๋ ํธ๋ ์ด๋์คํ๋ฅผ ๋ณด์
- ํ์ต ๋จ๊ณ ์์ด ๊ทธ๋ํ๋ฅผ ๊ตฌ์ถํ๋ฏ๋ก ํ ์ด๋ธ์ ๋ฐ์ดํฐ๊ฐ ์์ด๋ ์ธ๋ฑ์ค ์์ฑ์ด ๊ฐ๋ฅํ๊ณ , ์์ฑ ์ดํ์๋ ๋์ ์ฝ์ ์ด๋ ์ญ์ /์ ๋ฐ์ดํธ๊ฐ ๊ฐ๋ฅํ ๋์ ์ธ๋ฑ์ค
- ๋ค๋ง ์ธ๋ฑ์ค ๋น๋์ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ํฐ ์ ์ด ๋จ์
- ๋ฒกํฐ ์ฐจ์๊ณผ ๊ฑฐ๋ฆฌํจ์์ ๋ง๋ ์ฐ์ฐ์ ํด๋์ค๋ฅผ ์ง์ ํด์ผ ํจ (์: vector_l2_ops, vector_ip_ops ๋ฑ)
- ํ๋ ํ๋ผ๋ฏธํฐ
M
: ํ๋์ ๋ ธ๋๊ฐ ๊ฐ๋ ์ด์ ๋ ธ๋ ๊ฐ์๋ก ์ถ์ฒ ๋ฒ์๋ 5~48- ๊ฐ์ด ์ปค์ง์๋ก
- ๋น๋ ์๊ฐ, ๊ฒ์ ์๊ฐ, ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ
- ๊ทธ๋ํ ํ์ง, ์ ํ๋ ํฅ์
- ๊ฐ์ด ์ปค์ง์๋ก
ef_construction
: ๋น๋ ์ ์ด์ ๋ ธ๋๋ฅผ ์ ์ฅํ๋ ํ๋ณด ๋ฆฌ์คํธ ํฌ๊ธฐ- ๊ฐ์ด ์ปค์ง์๋ก
- ๋น๋ ์๊ฐ ์ฆ๊ฐ
- ๊ทธ๋ํ ํ์ง, ์ ํ๋ ํฅ์, ๊ฒ์ ์๊ฐ ๊ฐ์
- ๊ฐ์ด ์ปค์ง์๋ก
ef
: ๊ฒ์ ์ ๋ฐฉ๋ฌธํ ์ด์ ๋ ธ๋๋ฅผ ์ ์ฅํด๋๋ ๋์ ํ- ํด๋น ๊ฐ ๋งํผ์ ๋ ธ๋๋ฅผ ํ์
- ๊ฒ์ ๊ฐ์์ธ K๋ณด๋ค ์ปค์ผํจ
- ๋ฐ์ดํฐ ๊ฒฝํฅ์ฑ : ํด๋ฌ์คํฐ๋ง์ด ์ด๋์ ๋ ๋์ด์๋ ๋ฐ์ดํฐ์ผ์๋ก ์๋ต ์๋ ๋น ๋ฅด๊ณ ์ฌํ์จ ๋์
IVFFlat (Inverted File Index)
- ๋ฒกํฐ๋ค์ ๋ฏธ๋ฆฌ ์ฌ๋ฌ ํด๋ฌ์คํฐ ๋ฆฌ์คํธ๋ก ๋ถํ ํ์ฌ ๊ฒ์ ์ ์ผ๋ถ ๋ฆฌ์คํธ๋ง ํ์
- k-ํ๊ท ๊ตฐ์งํ๋ฅผ ํตํด ๋ฒกํฐ ๊ณต๊ฐ์ ๋ฏธ๋ฆฌ ๋ถํ ํ๋ฏ๋ก ์ธ๋ฑ์ค ์์ฑ ์ ์ฝ๊ฐ์ ํ์ต ์๊ฐ์ด ๋ค์ง๋ง, HNSW์ ๋นํด ์ธ๋ฑ์ค ์์ฑ ์๋๊ฐ ๋น ๋ฅด๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ด ์ ์ ์ฅ์
- ๋์ ๊ฒ์ ์ ํ๋๋ฅผ ๋์ด๋ ค๋ฉด ๋ ๋ง์ ๋ฆฌ์คํธ๋ฅผ ์ดํด๋ด์ผ ํ๋ฏ๋ก ๋์ผ ์์ค์ ์ ํ๋์์๋ HNSW๋ณด๋ค ๋๋ฆด ์ ์์
- IVFFlat ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค๋ ค๋ฉด ๋ช ๊ฐ์ ๋ฆฌ์คํธ๋ก ๋ถํ ํ ์ง(lists ๋งค๊ฐ๋ณ์) ์ค์ ํด์ผ ํ๋๋ฐ, ๋ณดํต ๋ฐ์ดํฐ ๊ฑด์์ โN ์์ค์ด๋ N/1000 ์์ค์ผ๋ก ์์ํ์ฌ ์กฐ์
- ๋ฆฌ์คํธ ์๊ฐ ๋ง์์๋ก ์ธ๋ฑ์ค ํฌ๊ธฐ๊ฐ ์ปค์ง๊ณ ๊ตฌ์ถ ์๊ฐ์ด ๋์ง๋ง, ์ ํ๋๋ ๋์์ง ์ ์์
- ์ฟผ๋ฆฌ ์์๋ SET ivfflat.probes = P๋ก ํ์ํ ๋ฆฌ์คํธ ๊ฐ์(๊ธฐ๋ณธ 1๊ฐ)๋ฅผ ์ง์ ํ ์ ์๋๋ฐ, probes๋ฅผ ๋๋ฆด์๋ก ๋ ๋ง์ ํ๋ณด๋ฅผ ํ์ธํ์ฌ ์ ํ๋๊ฐ ์ฌ๋ผ๊ฐ์ง๋ง ์๋๋ ๊ฐ์
- ๊ทน๋จ์ ์ผ๋ก probes๋ฅผ ๋ฆฌ์คํธ ์ด ๊ฐ์๋งํผ ์ฃผ๋ฉด ์ ์ฒด ๋ฆฌ์คํธ๋ฅผ ๋ค ๋ค์ง๋ ์ ์ด ๋์ด ์ ํํ ๊ฒ์๊ณผ ๋์ผ (์ด ๊ฒฝ์ฐ Planner๋ ์ธ๋ฑ์ค๋ฅผ ์ฐ์ง ์๊ณ Sequential Scan์ผ๋ก ๊ฐ์ฃผ)
- ์ ์ ํ probes ๊ฐ(์: ๊ธฐ๋ณธ๊ฐ 1์์ ์์ํด ์ ํ๋๊ฐ ๋ถ์กฑํ๋ฉด ์ ์ฐจ ์ฆ๊ฐ)์ ์ฐพ๋ ๊ฒ์ด ์ค์
- IVFFlat๋ ์ธ๋ฑ์ค ์์ฑ ์ ๋ฒกํฐ ์ฐจ์์ ์ง์ ํ ์ฐ์ฐ์ ํด๋์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๋ฉฐ ์์ฑ์ HNSW์ ๋์ผํ DDL ๋ฌธ๋ฒ์ ๋ฐ๋ฆ
- ์ฐธ๊ณ ๋ก ์ด๊ธฐ์ ๋ฐ์ดํฐ๊ฐ ์ถฉ๋ถํ ์์ธ ํ ์ธ๋ฑ์ค๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ข์
- ๋๋ฌด ์ ์ ๋ฐ์ดํฐ๋ก ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค๋ฉด ๊ฐ ๋ฆฌ์คํธ์ ๋ฒกํฐ๊ฐ ๊ณ ๋ฅด๊ฒ ๋ถํฌ๋์ง ์์ ์ฑ๋ฅ์ด ์ ํ๋ ์ ์์ผ๋ฏ๋ก, ์ด๋ฐ ๊ฒฝ์ฐ ์ผ์ ๋ ๋์ ํ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๊ฑฐ๋ ์ฌ์์ฑํ๋ ๊ฒ์ด ๊ถ์ฅ
5. ์ค์ต
pgvector & pgadmin ์ค๋น
pgvector๊ฐ ์ค์น๋ postgres์ pgadmin์ ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ pgvector๋ฅผ ์ฌ์ฉํด๋ณผ ์ ์๋ค
services:
postgres:
image: pgvector/pgvector:pg17
container_name: postgres-pgvector
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U postgres -d postgres
interval: 10s
timeout: 5s
retries: 5
# PGAdmin - PostgreSQL ๊ด๋ฆฌ ์น ์ธํฐํ์ด์ค
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
depends_on:
postgres:
condition: service_healthy
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
volumes:
- pgadmin_data:/var/lib/pgadmin
volumes:
postgres_data:
pgadmin_data:
http://localhost:5050
์ผ๋ก ์ ์ํ๋ฉด pgadmin ๋ก๊ทธ์ธ ํ๋ฉด์ด ๋์ค๊ณ docker-compose.yml
์ ์๋ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ ํ ์ ์๋ค
๊ทธ ํ ์ ์๋ฒ ์ถ๊ฐ
๋ฅผ ์ ํํ๊ณ [์ผ๋ฐ]์์ ์ด๋ฆ์ โpgvector-testโ, [์ฐ๊ฒฐ]์์ ํธ์คํธ ์ด๋ฆ/์ฃผ์๋ โpostgres-pgvectorโ, ํฌํธ๋ โ5432โ, ์ ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค/์ฌ์ฉ์์ด๋ฆ/๋น๋ฐ๋ฒํธ๋ ๋ชจ๋ โpostgresโ๋ก ์ค์ ํ๋ฉด ์ ์์ ์ผ๋ก postgreSQL๊ณผ ์ฐ๊ฒฐ์ด ๋๋ค
ํ ์ด๋ธ ์์ฑ ๋ฐ ๋ฐ์ดํฐ ์ฝ์
-- ================================
-- 0) ํ์ฅ ์ค์น ๋ฐ ์ค๋น
-- ================================
CREATE EXTENSION IF NOT EXISTS vector;
DROP TABLE IF EXISTS doc_vectors;
CREATE TABLE doc_vectors (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[],
emb VECTOR(3) -- ์์๋ผ 3์ฐจ์, ์ค์ ์์ 384/768/1536 ๋ฑ
);
-- ================================
-- 1) ์ํ ๋ฐ์ดํฐ ์ฝ์
-- - ์๋ก ์ ์ฌ/์์ดํ ์ฃผ์ ๋ฅผ ์์ด ๋ฐฐ์น
-- ================================
INSERT INTO doc_vectors (title, tags, emb) VALUES
('AI ๊ธฐ์ ์๊ฐ', ARRAY['ai','tech'], '[0.10, 0.20, 0.30]'),
('๋จธ์ ๋ฌ๋ ๊ฐ์', ARRAY['ai','ml'], '[0.20, 0.10, 0.40]'),
('๋ฅ๋ฌ๋ ๊ธฐ์ด', ARRAY['ai','dl'], '[0.12, 0.18, 0.28]'),
('์๋ฆฌ ๋ ์ํผ ๋ชจ์', ARRAY['food','recipe'], '[0.90, 0.80, 0.70]'),
('๋ฒ ์ดํน ์
๋ฌธ', ARRAY['food','baking'], '[0.85, 0.78, 0.69]'),
('์ ๋ฝ ์ฌํ ๊ฐ์ด๋', ARRAY['travel','guide'], '[0.55, 0.20, 0.15]');
-- ๊ธฐ์ค ๋ฌธ์ id ํ์ธ์ฉ
SELECT id, title FROM doc_vectors ORDER BY id;
- pgvector extension์ ์ค์น
- vector(3) ์๋ฃํ์ผ๋ก 3์ฐจ์ ๋ฒกํฐ ์๋ฃํ์ ์ ์ธ
- listํํ์ ๋ฒกํฐ๋ฅผ ๋ฃ์ด์ฃผ๋ฉด vector ์๋ฃํ์ผ๋ก ์ฝ์
L2 ๊ฑฐ๋ฆฌ๋ฅผ ํ์ฉํ ๊ฒ์ ์์
-- ================================
-- 2) L2 ๊ฑฐ๋ฆฌ (์ ํด๋ฆฌ๋) ๊ฒ์ ์์
-- ์ฐ์ฐ์: <-> (์์์๋ก ์ ์ฌ)
-- ================================
-- 2-1) ์ฟผ๋ฆฌ ๋ฒกํฐ๋ก Topโ3 ์ ์ฌ ๋ฌธ์
SELECT id, title, emb <-> '[0.15, 0.15, 0.30]'::vector AS l2_dist
FROM doc_vectors
ORDER BY l2_dist
LIMIT 3;
-- 2-2) ํน์ ๋ฌธ์(id=1)์ ์ ์ฌํ ๋ฌธ์ Topโ3
SELECT id, title,
emb <-> (SELECT emb FROM doc_vectors WHERE id = 1) AS l2_dist
FROM doc_vectors
WHERE id <> 1
ORDER BY l2_dist
LIMIT 3;
-- 2-3) ์๊ณ๊ฐ ๊ธฐ๋ฐ ํํฐ (L2 ๊ฑฐ๋ฆฌ๊ฐ 0.25 ์ดํ)
SELECT id, title
FROM doc_vectors
WHERE emb <-> '[0.15, 0.15, 0.30]'::vector <= 0.25
ORDER BY emb <-> '[0.15, 0.15, 0.30]'::vector;
::vector
๋ฅผ ์ด์ฉํด์ listํํ์ ๊ฐ์ ๋ฒกํฐ์๋ฃํ์ผ๋ก ๋ณํํด์ ๊ฒ์ ๊ฐ๋ฅ
Inner product๋ฅผ ํ์ฉํ ๊ฒ์ ์์
-- ================================
-- 3) ๋ด์ (Inner Product) ๊ธฐ๋ฐ ๊ฒ์ ์์
-- ์ฐ์ฐ์: <#> (์ฃผ์: "๋ถํธ๊ฐ ๋ฐ๋์ธ ๋ด์ ๊ฐ"์ ๋ฐํ)
-- ์ค๋ฆ์ฐจ์ ์ ๋ ฌ ์ ๋ด์ ๊ฐ์ด ํฐ(์ ์ฌํ) ์์์ ๋์ผํ ํจ๊ณผ
-- ================================
-- 3-1) ์ฟผ๋ฆฌ ๋ฒกํฐ๋ก Topโ3 (๋ด์ ๊ธฐ์ค)
SELECT id, title,
emb <#> '[0.15, 0.15, 0.30]'::vector AS neg_inner_product
FROM doc_vectors
ORDER BY neg_inner_product -- ์ค๋ฆ์ฐจ์ == ๋ด์ ํฐ ์
LIMIT 3;
-- 3-2) ํน์ ๋ฌธ์(id=1) ๊ธฐ์ค Topโ3 (๋ด์ ๊ธฐ์ค)
SELECT id, title,
emb <#> (SELECT emb FROM doc_vectors WHERE id = 1) AS neg_inner_product
FROM doc_vectors
WHERE id <> 1
ORDER BY neg_inner_product
LIMIT 3;
<#>
๋ฅผ ํ์ฉํด ๋ ๋ฒกํฐ ๊ฐ์ ๋ด์ ์ ๊ตฌํ๋ฉด ๊ฑฐ๋ฆฌ๊ฐ ๊ฐ๊น์ธ์๋ก ๊ฐ์ด ํผ- pgvector์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฒกํฐ๊ฐ ๊ฑฐ๋ฆฌ๊ฐ ๊ฐ๊น์ธ์๋ก ๊ฑฐ๋ฆฌ metric์ด ์๋๋ก ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ๊ณ์ฐ ๊ฒฐ๊ณผ๋ ์์๋ก ๋์ด
Cosine ๊ฑฐ๋ฆฌ๋ฅผ ํ์ฉํ ๊ฒ์ ์์
-- ================================
-- 4) ์ฝ์ฌ์ธ ๊ฑฐ๋ฆฌ/์ ์ฌ๋ ๊ธฐ๋ฐ ๊ฒ์ ์์
-- ์ฐ์ฐ์: <=> (์ฝ์ฌ์ธ "๊ฑฐ๋ฆฌ" = 1 - ์ฝ์ฌ์ธ "์ ์ฌ๋")
-- -> ์ฝ์ฌ์ธ ์ ์ฌ๋ = 1 - (emb <=> query)
-- ================================
-- 4-1) ์ฟผ๋ฆฌ ๋ฒกํฐ๋ก Topโ3 (์ฝ์ฌ์ธ)
SELECT id, title,
emb <=> '[0.15, 0.15, 0.30]'::vector AS cosine_distance,
1 - (emb <=> '[0.15, 0.15, 0.30]'::vector) AS cosine_similarity
FROM doc_vectors
ORDER BY cosine_distance
LIMIT 3;
-- 4-2) ํน์ ๋ฌธ์(id=1) ๊ธฐ์ค Topโ3 (์ฝ์ฌ์ธ)
SELECT id, title,
emb <=> (SELECT emb FROM doc_vectors WHERE id = 1) AS cosine_distance,
1 - (emb <=> (SELECT emb FROM doc_vectors WHERE id = 1)) AS cosine_similarity
FROM doc_vectors
WHERE id <> 1
ORDER BY cosine_distance
LIMIT 3;
- ๋ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ๊น์ธ์๋ก ๊ฐ์ด 1์ ๊ฐ๊น๊ธฐ ๋๋ฌธ์ 1์์ ๊ฐ์ ๋นผ์ ๊ฐ๊น์ธ์๋ก ์๊ฒ ํํ
๋ค๋ฅธ ์์ฑ์ ํํฐ๋ง๊ณผ ๊ฒฐํฉ
-- ================================
-- 5) ๋ฉํ๋ฐ์ดํฐ ํํฐ + ์ ์ฌ๋ ๊ฒ์ ๊ฒฐํฉ
-- (์: ํ๊ทธ์ 'ai' ํฌํจ ๋ฌธ์ ์ค์์๋ง ์ฝ์ฌ์ธ Topโ3)
-- ================================
SELECT id, title,
1 - (emb <=> '[0.15, 0.15, 0.30]'::vector) AS cos_sim
FROM doc_vectors
WHERE 'ai' = ANY(tags)
ORDER BY emb <=> '[0.15, 0.15, 0.30]'::vector
LIMIT 3;
- tags์ โaiโ๋ฅผ ํฌํจํ๋ ์ฑ ๋ด์์๋ง ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ์ฐํ ์ ์์
ANN ์ธ๋ฑ์ค ํ์ธ์ฉ ํ ์ด๋ธ ์์ฑ ๋ฐ ๋ฐ์ดํฐ ์ฝ์
CREATE TABLE big_vectors (
id bigserial PRIMARY KEY,
emb vector(1532),
payload text
);
CREATE OR REPLACE FUNCTION random_vector_1532()
RETURNS vector(1532)
AS $$
SELECT array_agg((random() * 2 - 1)::real ORDER BY i)::vector(1532)
FROM generate_series(1, 1532) AS g(i)
$$ LANGUAGE sql VOLATILE;
-- ๋ฐ์ดํฐ ์ฝ์
(10๋ง ํ ์์, ํ์์ ํ ์ ์กฐ์ ) : 25 secs 330 msec.
INSERT INTO big_vectors (emb, payload)
SELECT random_vector_1532(), md5(gs::text)
FROM generate_series(1, 100000) gs;
- 1532 ์ฐจ์์ ๋ฒกํฐ๋ฅผ ๋ด์ ์ ์๋ ํ ์ด๋ธ ์์ฑ
- ๋๋คํ๊ฒ 1532 ์ฐจ์์ ๋ฒกํฐ๋ฅผ ์์ฑํ ์ ์๋ UDF ์ ์
- ์ ์ํ UDF๋ก 100,000๊ฐ์ ๋ฐ์ดํฐ ์ฝ์
ANN ์ธ๋ฑ์ค ์์ฑ ์ Exact Matching ์ฑ๋ฅ ํ์ธ
-- ๋ถ์ ํต๊ณ ์
๋ฐ์ดํธ
VACUUM ANALYZE big_vectors;
-- ์ฟผ๋ฆฌ ๋ฒกํฐ ๊ณ ์
DROP TABLE IF EXISTS qvec;
CREATE UNLOGGED TABLE qvec AS
SELECT random_vector_1532() AS q;
-- ์ธ๋ฑ์ค ์์ด ์ ํํ์ ์ฑ๋ฅ
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, emb <-> (SELECT q FROM qvec)
FROM big_vectors
ORDER BY emb <-> (SELECT q FROM qvec)
LIMIT 10;
VACUUM
์ผ๋ก dead tuple ์ ๋ฆฌํ๊ณANALYZE
๋ก big_vectors ํ ์ด๋ธ์ ํต๊ณ ์ ๋ณด ์ ๋ฐ์ดํธ- ์ฟผ๋ฆฌ์ ์ฌ์ฉํ ๋ฒกํฐ ์์ฑํด์ WAL์ ์ ์ฅ์๋๋ UNLOGGED ํ ์ด๋ธ์ ์ ์ฅ
- ์ฌ์ ์ ์์ฑํ ์ฟผ๋ฆฌ ๋ฒกํฐ์ ์ ์ฌ๋ ๊ธฐ๋ฐ ๊ฒ์ ์ํ โ โParallel Seq Scanโ ์ํ
์ธ๋ฑ์ค ์ฌ์ฉ X Limit (cost=4167.07..4168.22 rows=10 width=16) (actual time=883.367..885.444 rows=10 loops=1) Buffers: shared hit=499279 read=101982 written=4883 InitPlan 1 -> Seq Scan on qvec (cost=0.00..23.60 rows=1360 width=32) (actual time=0.009..0.009 rows=1 loops=1) Buffers: shared hit=1 -> Gather Merge (cost=4143.47..10908.23 rows=58824 width=16) (actual time=883.365..885.440 rows=10 loops=1) Workers Planned: 1 Workers Launched: 1 Buffers: shared hit=499279 read=101982 written=4883 -> Sort (cost=3143.46..3290.52 rows=58824 width=16) (actual time=878.597..878.599 rows=8 loops=2) Sort Key: ((big_vectors.emb <-> (InitPlan 1).col1)) Sort Method: top-N heapsort Memory: 25kB Buffers: shared hit=499278 read=101982 written=4883 Worker 0: Sort Method: top-N heapsort Memory: 25kB -> Parallel Seq Scan on big_vectors (cost=0.00..1872.30 rows=58824 width=16) (actual time=0.249..869.867 rows=50000 loops=2) Buffers: shared hit=499241 read=101982 written=4883 Planning: Buffers: shared hit=4 Planning Time: 0.169 ms Execution Time: 885.477 ms
HNSW ์ธ๋ฑ์ค ์์ฑ ๋ฐ ๋์ผํ ์ฟผ๋ฆฌ ์ํ
-- HNSW ์ธ๋ฑ์ค ์์ฑ
DROP INDEX IF EXISTS idx_big_vectors_hnsw;
CREATE INDEX idx_big_vectors_hnsw
ON big_vectors USING hnsw (emb vector_l2_ops)
WITH (m = 16, ef_construction = 200);
ANALYZE big_vectors;
-- HNSW ์ฟผ๋ฆฌ (ef_search ๋ณ๊ฒฝ ๊ฐ๋ฅ)
SET hnsw.ef_search = 80;
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, emb <-> (SELECT q FROM qvec)
FROM big_vectors
ORDER BY emb <-> (SELECT q FROM qvec)
LIMIT 10;
- ์ธ๋ฑ์ค ์์ฑ์ 12๋ถ 30์ด ์ ๋ ์์
- ์ธ๋ฑ์ค ์์ฑ๊ณผ์ ์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ด๊ณผํ๋ฉด ๋์คํฌ ์์ ํ์ผ์ ์ฌ์ฉํด์ ๊ณ์ ์งํ โ
NOTICE:ย hnsw graph no longer fits into maintenance_work_mem after 9239 tuples
- ์ธ๋ฑ์ค ๋น๋ ์๋๋ ๋๋ ค์ง ์ ์์ง๋ง ์์ฑ๋ ์ธ๋ฑ์ค ์์ฒด์ ์ฑ๋ฅ์๋ ํฐ ์ํฅ ์์
- ์ธ๋ฑ์ค ์์ฑ๊ณผ์ ์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ด๊ณผํ๋ฉด ๋์คํฌ ์์ ํ์ผ์ ์ฌ์ฉํด์ ๊ณ์ ์งํ โ
- ์ ์ฌ๋ ๊ธฐ๋ฐ ๊ฒ์ ์ํ โ HNSW ์ธ๋ฑ์ค ์ฌ์ฉ
HNSW index Limit (cost=1169.23..1209.77 rows=10 width=16) (actual time=4.072..4.189 rows=10 loops=1) Buffers: shared hit=3247 InitPlan 1 -> Seq Scan on qvec (cost=0.00..23.60 rows=1360 width=32) (actual time=0.006..0.007 rows=1 loops=1) Buffers: shared hit=1 -> Index Scan using idx_big_vectors_hnsw on big_vectors (cost=1145.63..406552.00 rows=100000 width=16) (actual time=4.071..4.186 rows=10 loops=1) Order By: (emb <-> (InitPlan 1).col1) Buffers: shared hit=3247 Planning: Buffers: shared hit=1 Planning Time: 0.068 ms Execution Time: 4.207 ms
IVFFlat ์ธ๋ฑ์ค ์์ฑ ๋ฐ ๋์ผํ ์ฟผ๋ฆฌ ์ํ
-- ๊ธฐ์กด hnsw ์ธ๋ฑ์ค ์ ๊ฑฐ
DROP INDEX IF EXISTS idx_big_vectors_hnsw;
-- IVFFlat ์ธ๋ฑ์ค ์์ฑ
DROP INDEX IF EXISTS idx_big_vectors_ivf;
CREATE INDEX idx_big_vectors_ivf
ON big_vectors USING ivfflat (emb vector_l2_ops)
WITH (lists = 80);
ANALYZE big_vectors;
-- IVFFlat ์ฟผ๋ฆฌ (probes ๋ณ๊ฒฝ ๊ฐ๋ฅ)
SET ivfflat.probes = 10;
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, emb <-> (SELECT q FROM qvec)
FROM big_vectors
ORDER BY emb <-> (SELECT q FROM qvec)
LIMIT 10;
- ์ธ๋ฑ์ค๋ฅผ ์ฌ๋ฌ ๊ฐ ์ค์ ํ ์ ์๊ธด ํ์ง๋ง ์ฌ๊ธฐ์์๋ hnsw ์ธ๋ฑ์ค ์ ๊ฑฐ
- IVFFlat ์ธ๋ฑ์ค๋ ๋ฉ๋ชจ๋ฆฌ ์ด๊ณผํ ๊ฒฝ์ฐ ๋ฐ๋ก Error โ
ERROR:ย memory required is 506 MB, maintenance_work_mem is 64 MB
- ์ธ๋ฑ์ค ๋น๋ํ ๋์
SET LOCAL maintenance_work_mem = '1GB';
์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํฌ๊ธฐ ํ์ฅ - lists ๊ฐ ๋ฎ์ถฐ์ ๋ฉ๋ชจ๋ฆฌ ์๊ตฌ๋ ๊ฐ์
- ์ธ๋ฑ์ค ๋น๋ํ ๋์
- ์ ์ฌ๋ ๊ธฐ๋ฐ ๊ฒ์ ์ํ โ IVFFlat ์ธ๋ฑ์ค ์ฌ์ฉ
IVFFlat index Limit (cost=1254.35..1279.90 rows=10 width=16) (actual time=92.153..92.239 rows=10 loops=1) Buffers: shared hit=7490 read=39200 InitPlan 1 -> Seq Scan on qvec (cost=0.00..23.60 rows=1360 width=32) (actual time=0.005..0.006 rows=1 loops=1) Buffers: shared hit=1 -> Index Scan using idx_big_vectors_ivf on big_vectors (cost=1230.75..256750.50 rows=100000 width=16) (actual time=92.151..92.236 rows=10 loops=1) Order By: (emb <-> (InitPlan 1).col1) Buffers: shared hit=7490 read=39200 Planning: Buffers: shared hit=1 Planning Time: 0.073 ms Execution Time: 92.257 ms
Footnotes
-
์ฌ๋ฌ worker process๊ฐ ๋์์ ํ ์ด๋ธ ๋๋์ด ์ฝ๋ ๋ฐฉ์ โฉ
-
Write Ahead Log. ๋ณ๊ฒฝ ๋ก๊ทธ ์์คํ ์ผ๋ก ์ค์ ๋ฐ์ดํฐ ํ์ผ ์์ ํ๊ธฐ ์ ์ ๋จผ์ ๋ก๊ทธ์ ๊ธฐ๋กํ๊ณ ์ดํ ๋ฐ์ดํฐ ํ์ผ์ ๋ฐ์ํด์ โ์ฅ์ ๋ณต๊ตฌ์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅโ โฉ
-
Point in Time Recovery. WAL ๋ก๊ทธ๋ฅผ ์ด์ฉํด ํน์ ์์ ์ผ๋ก DB ๋ณต๊ตฌํ๋ ๊ฒ โฉ
-
Primary์์ Standby ์๋ฒ ๊ฐ์ ์ค์๊ฐ์ผ๋ก WAL ๋ก๊ทธ๋ฅผ ์ ๋ฌํ์ฌ ๋ณต์ ํ๋ ๋ฐฉ์ โฉ