[PostgreSQL] tsvector์™€ Full Text Search

[์›๋ณธ ๋งํฌ]

PostgreSQL์—์„œ ์ „๋ฌธ๊ฒ€์ƒ‰(Full Text Search)์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ณธ๋‹ค.

๋‹จ์ˆœ ํฌํ•จ ๊ฒ€์ƒ‰์ด๋ผ๋ฉด Like์™€ pg_trgm์„ ์จ๋„ ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ๋” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋‹จ์–ด ์ˆ˜์ค€ ์ž์—ฐ์–ด ๊ฒ€์ƒ‰์„ ์›ํ•œ๋‹ค๋ฉด ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์ด ๋ฐฉ๋ฉด์—์„œ PostgreSQL์€ Elasticseach/Lucene์— ๋น„ํ•  ๋ฐ”๋Š” ์•„๋‹ˆ์ง€๋งŒ, ์ ๋‹นํ•œ ์ˆ˜์ค€์—์„œ๋Š” ์“ธ๋งŒํ•œ ํ…์ŠคํŠธ ๋ถ„์„&๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

๋‹จ, ๋‹ค๊ตญ์–ด์— ๋Œ€ํ•œ ์›๋งŒํ•œ ์ฒ˜๋ฆฌ๋‚˜ ์—„์ฒญ๋‚œ ๊ณ ์„ฑ๋Šฅ ์ธ๋ฑ์‹ฑ์„ ๊ธฐ๋Œ€ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.
์ž‘๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ์— ์ข‹์€ ์ •๋„๋ผ์„œ, ์š”๊ตฌ์‚ฌํ•ญ์ด ์–ด๋А ์ •๋„์ธ์ง€๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์„ ํƒํ•ด์•ผ ํ•œ๋‹ค.




ํ…Œ์ด๋ธ” & ๋ฐ์ดํ„ฐ ์…‹์—…

๋จผ์ € ํ…Œ์ด๋ธ”์„ ์ ๋‹นํžˆ ์ •์˜ํ•˜์ž.
์ผ๋‹จ ํ•„์š”ํ•œ ๊ฒƒ์€ ํ…์ŠคํŠธ๋‹ค. ํ…์ŠคํŠธ๊ฐ€ ์žˆ์–ด์•ผ ์ชผ๊ฐœ์„œ ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

CREATE TABLE search_set (
  id bigserial PRIMARY KEY,
  user_id bigint NOT NULL,
  original_text text NOT NULL,
  search_vector tsvector
);

์ ๋‹นํžˆ ๊น”์•˜๋‹ค.

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ tsvector๋ผ๋Š” ์ „์šฉ ํƒ€์ž…์ด๋‹ค. ์ €๊ฑธ ๊ฒ€์ƒ‰ ์ตœ์ ํ™”์šฉ ์ปฌ๋Ÿผ์œผ๋กœ ๋ณ„๋„ ์„ค์ •์„ ํ•ด์•ผ ํ•œ๋‹ค.




ํ† ํฐํ™” ๋‹จ์œ„ (tsvector)

๊ทธ ๋‹ค์Œ์— ํ•ด์•ผํ•  ๊ฒƒ์€ ํ…์ŠคํŠธ๋ฅผ ๋‹จ์–ด ๋‹จ์œ„๋กœ ์ชผ๊ฐœ๋Š” ๊ฒƒ์ด๋‹ค.
elasticsearch ๋“ฑ์—์„œ tokenizer๋ผ๊ณ  ๋ถ€๋ฅด๋Š”๋ฐ ์ด๊ฑด๋ฐ, ํ† ํฐ ๋‹จ์œ„๊ฐ€ tsvector๋ผ๋Š” ํƒ€์ž…์„ ํ†ตํ•ด ํ‘œํ˜„๋œ๋‹ค.

to_tsvector๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ…์ŠคํŠธ๋ฅผ ๊ทธ ์ฆ‰์‹œ ๋‹จ์–ด์˜ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” simple tokenizer๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฃจํ”„๋ฅผ ๋Œ๋ ค๋ณด๊ฒ ๋‹ค.

-- SELECT to_tsvector('ํ† ํฐํ™” ๋ฐฉ์‹', 'ํ…์ŠคํŠธ');
SELECT to_tsvector('simple', 'He is good dog');

๊ทธ๋Ÿผ ์ด๋Ÿฐ ์‹์œผ๋กœ, ๋‹จ์–ด๋ฅผ ์ชผ๊ฐ  ๋‹ค์Œ์— ๋‹จ์–ด๋ณ„ ๊ฐœ์ˆ˜๋ฅผ ํ‘œ์‹œํ•ด์ค€๋‹ค.

์•„๋ฌดํŠผ ์ ๋‹นํžˆ ํ•„์š”ํ•œ ํ† ํฐํ™” ๋ฐฉ์‹์„ ์„ ํƒํ•ด์„œ, ๋ฒกํ„ฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ค.

update search_set
set search_vector = to_tsvector('simple', original_Text)

๋Œ€์ถฉ ๋Œ๋ ค๋งŒ ๋ณด๋ ค๋ฉด ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋˜๊ธด ํ•˜๋Š”๋ฐ, ๋‹ค๊ตญ์–ด์ฒ˜๋ฆฌ๋‚˜ ์ด๋Ÿฐ์ €๋Ÿฐ ์š”๊ตฌ์‚ฌํ•ญ์ด ์žˆ๋‹ค๋ฉด ํ† ํฐํ™”๋ฅผ DB ๋ฐ–์—์„œ ํ•˜๊ณ  tsvector ํ‘œํ˜„์‹๋งŒ ๋„ฃ์–ด์ฃผ๋Š”๊ฒŒ ์ข‹๋‹ค.

๋งŒ์•ฝ ์ง์ ‘ ํ† ํฐํ™”๋ฅผ ํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ์‹์œผ๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

select '''openai'':1,3 ''gpt'':2'::tsvector

ํ‚ค:์œ„์น˜1,์œ„์น˜2, ... ์Œ์ด๋‹ค.




tsquery๋ฅผ ํ†ตํ•œ ์ฟผ๋ฆฌ ํ‘œํ˜„

๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ์„ธํŒ…ํ–ˆ์œผ๋ฉด, ์ด์ œ ๊ฒ€์ƒ‰์„ ํ•  ์ฐจ๋ก€๋‹ค.
์ด๊ฑด ๊ฒ€์ƒ‰ ํ‘œํ˜„์‹ ์ž์ฒด๋ฅผ ๋˜ tsquery๋ผ๋Š” ํ•˜๋‚˜์˜ ํƒ€์ž…์œผ๋กœ ์ œ๊ณตํ•œ๋‹ค.

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ๋ฒ•์€, to_tsquery๋ผ๋Š” ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ด๋‹ค.

SELECT to_tsquery('simple', 'openai & gpt');

์ด๊ฑด openai์™€ gpt๊ฐ€ ๋ชจ๋‘ ํฌํ•จ๋œ ํ…์ŠคํŠธ๋ฅผ ์ฐพ๋Š” ํ‘œํ˜„์‹์ด ๋œ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” ํ† ํฐํ™” ๋งค์ปค๋‹ˆ์ฆ˜์ด ํฌ๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š๋‹ค. ์ € ๊ฒ€์ƒ‰์–ด๋ฅผ ํ† ํฐ์œผ๋กœ ์ชผ๊ฐœ๋Š” ๊ธฐ์ค€์ด ๋  ๋ฟ์ด๋‹ค.


์›ํ•œ๋‹ค๋ฉด ๋ฆฌํ„ฐ๋Ÿด์€ ์ด๋Ÿฐ ์‹์œผ๋กœ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•˜๋‹ค.




๊ฒ€์ƒ‰ํ•˜๊ธฐ: ๋‹จ์–ด ํ•„ํ„ฐ๋ง ๊ฒ€์ƒ‰

๊ฒ€์ƒ‰์„ ํ•  ๋•Œ๋Š” @@ ์—ฐ์‚ฐ์ž๋กœ ๋Œ€์ƒ ๋ฒกํ„ฐ์— tsquery๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋œ๋‹ค.

๋ฒกํ„ฐํ•„๋“œ @@ ์ฟผ๋ฆฌ


A. AND ๊ฒ€์ƒ‰: &

AND ์ ˆ ๊ฐ™์€ ๊ฒƒ๋„ ์›ํ•˜๋Š”๋Œ€๋กœ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
&๋ฅผ ๋ถ™์ด๋ฉด ๋‹จ์–ด๊ฐ€ ์ „๋ถ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ๋งŒ ์ฐพ๋Š”๋‹ค.

์•„๋Ÿฌ๋ฉด ์ด์ œ ์น˜ํ‚จ๊ณผ ํŠธ๋ Œ๋“œ๊ฐ€ ์ „๋ถ€ ํฌํ•จ๋œ ๋ฒกํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง์ด ๋œ๋‹ค. &๊ฐ€ AND ์—ฐ์‚ฐ์ž๋‹ค.


B. OR ๊ฒ€์ƒ‰: |

| ๋ฅผ ๋„ฃ์œผ๋ฉด OR ๊ฒ€์ƒ‰๋„ ๋œ๋‹ค. ์ด๊ฒƒ๋„ ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ๋ฉด ์ž˜ ํƒ„๋‹ค.


C. Phrase ๊ฒ€์ƒ‰: <->

phrase๋Š” ํŠน์ • ๋‹จ์–ด ๋ญ‰์น˜๊ฐ€ ์—ฐ์†์ ์œผ๋กœ ๋“ฑ์žฅํ•˜๋Š” ๊ฒƒ์„ ์œ„ํ•œ ์กฐ๊ฑด์ด๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ, A ๋‹จ์–ด ๋’ค์— ๋ฐ”๋กœ B ๋‹จ์–ด๊ฐ€ ์ด์–ด์ง€๋Š” ๊ฒƒ๋งŒ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด A <-> B์˜ ํ˜•ํƒœ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.


D. Distance ๊ฒ€์ƒ‰:

๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰์ด๋ž€ ๊ฒƒ์ด ์žˆ๋‹ค. ์ด๊ฑด phrase๋ณด๋‹ค ํ™•์žฅ๋œ ๊ตฌ์กฐ์ธ๋ฐ, ๋‹จ์–ด ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ๊ฐ€ ์ข€ ์žˆ์–ด๋„ ํฌํ•จ์ด ๋˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ ๊ฑฐ๋ฆฌ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

select 
	to_tsvector('simple', '๋นจ๊ฐ„์ƒ‰ ๋…ธ๋ž€์ƒ‰') @@ to_tsquery('simple', '๋นจ๊ฐ„์ƒ‰ <2> ๋…ธ๋ž€์ƒ‰') as _0 
	, to_tsvector('simple', '๋นจ๊ฐ„์ƒ‰ ๋•ก ๋…ธ๋ž€์ƒ‰') @@ to_tsquery('simple', '๋นจ๊ฐ„์ƒ‰ <2> ๋…ธ๋ž€์ƒ‰') as _1
	, to_tsvector('simple', '๋นจ๊ฐ„์ƒ‰ ๋•ก ๋•ก ๋…ธ๋ž€์ƒ‰') @@ to_tsquery('simple', '๋นจ๊ฐ„์ƒ‰ <2> ๋…ธ๋ž€์ƒ‰') as _2


E. Prefix ๊ฒ€์ƒ‰: :*

๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒ€์ƒ‰์€ ๋‹จ์–ด(ํ† ํฐ) ์ˆ˜์ค€์˜ Exact match๋‹ค.
ํ•˜์ง€๋งŒ tsvector๋Š” Prefix ํฌํ•จ ๊ฒ€์ƒ‰ ๋˜ํ•œ ์ง€์›ํ•œ๋‹ค. ๊ทธ๋ƒฅ "์น˜ํ‚จ"๋งŒ ๋„ฃ๋Š”๋‹ค๋ฉด ์น˜ํ‚จ ๋‹จ์–ด๊ฐ€ ์žˆ๋Š” ๊ฒƒ๋งŒ ๋งค์นญ๋˜์ง€๋งŒ, :*๋ฅผ ๋ถ™์ด๋ฉด ์น˜ํ‚จ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‹จ์–ด๋“ค๋„ ํ•จ๊ป˜ ๋งค์นญ๋œ๋‹ค.




Score๋กœ ์ •๋ ฌํ•˜๊ธฐ

๋‹จ์–ด์˜ ํฌํ•จ ๊ฐœ์ˆ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋žญํ‚น์„ ํ•˜๊ฑฐ๋‚˜ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
ํ•ต์‹ฌ์€ to_rank์™€ to_rank_cd๋ผ๋Š” ํ•จ์ˆ˜๋‹ค.

์ด๊ฑด ์ฟผ๋ฆฌ ํ‘œํ˜„์‹์„ ๊ธฐ๋ฐ˜์œผ๋กœ, ์–ผ๋งˆ๋‚˜ ๋‹จ์–ด๊ฐ€ ๋งค์นญ๋˜๋Š”์ง€๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.

ts_rank(tsvector, tsquery)

๋‹น์—ฐํžˆ ๋‹จ์–ด๊ฐ€ ๋งŽ์ด ๋งค์นญ๋ ์ˆ˜๋ก ์ ์ˆ˜๊ฐ€ ๋†’์•„์ง€๊ณ , ์—†๋‹ค๋ฉด 0์ ์ด ๋‚˜์˜จ๋‹ค.

to_rank_cd๋Š” to_rank์™€ ๋น„์Šทํ•˜๊ธด ํ•œ๋ฐ, ๊ฒ€์ƒ‰์–ด๋“ค๋ผ๋ฆฌ ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๋” ํฐ ์ ์ˆ˜๊ฐ€ ๋‚˜์˜จ๋‹ค.
๊ทธ๋Ÿฌ๋‹ˆ๊นŒ, "์‚ผ์„ฑ์ „์ž"์™€ "ai"๋ฅผ AND ์กฐ๊ฑด์œผ๋กœ ๋„ฃ์—ˆ์„๋•Œ, ๋ฐ์ดํ„ฐ์— ๊ทธ 2๊ฐœ ์‚ฌ์ด์— ๋‹จ์–ด๊ฐ€ ์ ์„์ˆ˜๋ก ์ ์ˆ˜๊ฐ€ ์ปค์ง€๊ณ , ๋‹จ์–ด๊ฐ€ ๋งŽ์•„์„œ ๋ฉ€์–ด์งˆ์ˆ˜๋ก ์ ์ˆ˜๊ฐ€ ์ž‘์•„์ง„๋‹ค.

์‹ค์ œ ์‚ฌ์šฉ๋ก€์—์„œ๋Š” ์ผ๋ฐ˜ ๋ฒ„์ „๊ณผ cd ๋ฒ„์ „ ๋ชจ๋‘ ์œ ์šฉํ•˜๋‹ค.

์•„๋ฌดํˆฐ ์ €๊ฑธ ์ด์ œ ์ ์ ˆํžˆ ORDER BY ์ ˆ์—๋งŒ ๋ถ™์ด๋ฉด ์ •๋ ฌ์ด ๋œ๋‹ค.

๊ทผ๋ฐ, ์„ฑ๋Šฅ์ ์œผ๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค.
ts_rank๋กœ ์ •๋ ฌ์„ ๊ฑฐ๋Š”๊ฑด ํ›„์ˆ ํ•  tsvector ์ธ๋ฑ์‹ฑ์˜ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค. ์–ด์ฉ” ์ˆ˜ ์—†๋‹ค. tsvector์šฉ ์ธ๋ฑ์Šค๋Š” ํ•„ํ„ฐ๋ง์šฉ์ด์ง€, ์ •๋ ฌ์ด๋‚˜ ๊ฐ€์ค‘์น˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์‹ค์ œ๋กœ ์“ธ๋•Œ๋Š” ๋จผ์ € ํ•„ํ„ฐ๋ง์„ ํ•˜๊ณ  ํ›„์ฒ˜๋ฆฌ๋กœ ์ •๋ ฌ์„ ํ•˜๋Š” ํŽธ์ด ์ข‹๋‹ค. ์•ˆ๊ทธ๋Ÿฌ๋ฉด 0์ ์งœ๋ฆฌ๋„ ์ „๋ถ€ ๊ณ„์‚ฐํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ ค์„œ ์“ฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋Ÿผ ์ด๋ ‡๊ฒŒ ๋น ๋ฅด๊ฒŒ ํƒ„๋‹ค. ์น˜ํ‚จ์ด ํฌํ•จ๋œ ๊ฒƒ๋งŒ ๋ถ€๋ถ„์ ์œผ๋กœ ๋กœ๋“œํ•œ ๋‹ค์Œ์— ์ •๋ ฌ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.




Gin ์ธ๋ฑ์Šค

tsvector ํƒ€์ž…์€ ์ „์šฉ ์ธ๋ฑ์Šค ํ˜•์‹์„ ์ง€์›ํ•œ๋‹ค. GIN๊ณผ Gist 2๊ฐ€์ง€๊ฐ€ ๊ฐ€๋Šฅํ•œ๋ฐ, ์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—๋Š” ์†์‹ค์ด ์—†๋Š” GIN์„ ๊ถŒ์žฅํ•œ๋‹ค.
์ด๊ฑด ์ผ๋ฐ˜ ์ปฌ๋Ÿผ ์กฐ๊ฑด๊ณผ ํ˜ผํ•ฉํ•ด์„œ ์ปค๋ฒ„๋ง์œผ๋กœ ์“ธ ์ˆ˜๋Š” ์—†๋‹ค.

CREATE INDEX  idx_search_set_vector
ON search_set
USING gin (search_vector);

์ด๋ ‡๊ฒŒ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค. ์ด๊ฒŒ ํ…์ŠคํŠธ๊ฐ€ ํฌ๋ฉด ๋นŒ๋“œํƒ€์ž„์ด ๊ฝค ๋ฌด๊ฒ๋‹ค.
์ˆ˜์‹ญ๋งŒ ํ–‰ ์ •๋„์ธ๋ฐ๋„ ๊ณ ์„ฑ๋Šฅ ์žฅ๋น„์—์„œ ๋ช‡๋ถ„์ด ๊ฑธ๋ ธ๋‹ค.


๊ทธ๋Ÿผ ์›๋ž˜ ์ดˆ ๋‹จ์œ„๋กœ๋„ ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์ด,


(์›ฌ๋งŒํ•ด์„œ๋Š”) ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๊ฑด tsvector @@ tsquery ํ˜•ํƒœ์˜ ๋‹จ์ˆœ ํ•„ํ„ฐ๋ง ์กฐ๊ฑด์—๋งŒ ์ธ๋ฑ์‹ฑ์ด ๋œ๋‹ค. ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ rank ๊ธฐ๋ฐ˜ ์ •๋ ฌ ๊ฐ™์€๊ฑด ์ตœ์ ํ™”์˜ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค.


์ฐธ๊ณ ๋กœ ์ธ๋ฑ์Šค ํฌ๊ธฐ๊ฐ€ ์ข€ ํฌ๋‹ค.
๋””์Šคํฌ ์••๋ฐ•์ด๋‚˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€๋ฅผ ๊ณ ๋ คํ•˜๊ธด ํ•ด์•ผ ํ•œ๋‹ค.




Gist ์ธ๋ฑ์Šค (๋น„์ถ”์ฒœ)

tsvector๋Š” Gist ๊ธฐ๋ฐ˜์˜ ์ธ๋ฑ์Šค๋„ ์ง€์›ํ•œ๋‹ค.
์ด๊ฑด tradeoff๊ฐ€ ํ™•์‹คํ•˜๊ฒŒ ์žˆ๋‹ค.

์žฅ์ : ์ธ๋ฑ์Šค ๋นŒ๋“œ๊ฐ€ ๋น ๋ฅด๊ณ  ์ธ๋ฑ์Šค ํฌ๊ธฐ๊ฐ€ ์ž‘์€ ๋Œ€์‹ 
๋‹จ์ : ์†๋„๊ฐ€ ๋А๋ฆฌ๊ณ , ์ธ๋ฑ์‹ฑ ๋‹จ๊ณ„์—์„œ ์†์‹ค์ด ๋ฐœ์ƒํ•œ๋‹ค.

์ด๊ฒŒ ์ข€ ๋ฏธ๋ฌ˜ํ•œ๋ฐ, ์ธ๋ฑ์‹ฑ์„ ํ• ๋•Œ "์น˜ํ‚จ" ๊ฒ€์ƒ‰์„ ํ•ด๋„ "์น˜ํ‚จ"์ด ์—†๋Š” ํ–‰์ด ๋ฐ˜ํ™˜๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋ฌผ๋ก  ๊ทธ๊ฒŒ ์‚ฌ์šฉ์ž์—๊ฒŒ๊นŒ์ง€ ๊ฐ€์ง„ ์•Š๊ณ , DB ๋‚ด๋ถ€์ ์œผ๋กœ ์ฒดํฌ๋ฅผ ํ•ด์„œ ๊ต์ •์„ ํ•œ๋‹ค.
๋‹ค๋งŒ ๋ถˆํ•„์š”ํ•œ ์Šค์บ”์ด๋‚˜ ๋ฐ˜๋ณต์ด ๋ฐœ์ƒํ•ด์„œ ๋น„ํšจ์œจ์ ์œผ๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.




์ œํ•œ์‚ฌํ•ญ ๋ฐ ํ•œ๊ณ„

tsquery&tsvector๋Š” ๊ธฐ๋Šฅ์ ์ธ ์ œํ•œ์‚ฌํ•ญ์ด๋‚˜ ํ•œ๊ณ„๊ฐ€ ์ข€ ์žˆ๋‹ค.


1. ํ† ํฐ๋‹น ๊ธธ์ด์ œํ•œ

tsvector๋ฅผ ์ƒ์„ฑํ• ๋•Œ, ํ•œ ๋‹จ์–ด(ํ† ํฐ) ๋‹น ๊ธธ์ด ์ œํ•œ์ด ์žˆ๋‹ค.
๋ณดํ†ต 2048์ž๊ฐ€ ์ œํ•œ์ด๊ณ , 2048์ž ์ด์ƒ์˜ ํ† ํฐ์€ tsvector๋กœ ๋ณ€ํ™˜๋˜์ง€ ์•Š๊ณ  ๋ฒ„๋ ค์ง„๋‹ค. ์˜ค๋ฅ˜๋Š” ์•ˆ๋œจ๊ณ  ๊ฒฝ๊ณ ๋งŒ ๋œฌ๋‹ค.


2. ๋ณต์žกํ•œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰์€ ๋ถˆ๊ฐ€๋Šฅ

Elasticsearch ๊ฐ™์€ ์ „๋ฌธ ์—”์ง„์—์„œ๋Š” BM25 ๊ฐ™์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ณ€์น™์ ์ธ ๊ฐœ๋…์„ ํ†ตํ•ด ์„ธ์‹ฌํ•œ ์ตœ์ ํ™”๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ, PostgreSQL์—๋Š” ๊ทธ ์ •๋„๊นŒ์ง€๋Š” ์—†๋‹ค. ๋„ˆ๋ฌด ๋งŽ์ด ๋ฐ”๋ผ๋ฉด ์•ˆ๋œ๋‹ค.


3. ์ •๋ ฌ ์ตœ์ ํ™” ๋ถˆ๊ฐ€๋Šฅ

์•ž์„œ ์–ธ๊ตฝํ–ˆ๋“ฏ์ด, ์Šค์ฝ”์–ด ์ •๋ ฌ์€ ์ธ๋ฑ์Šค๋ฅผ ์ตœ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ํ’€์Šค์บ”ํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฐ ๋’ค์— ์ •๋ ฌํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.


4. ๋ณตํ•ฉ์ ์ธ ํ•„ํ„ฐ๋ง ์ตœ์ ํ™” ๋ถˆ๊ฐ€๋Šฅ

Elasticsearch ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ํ…์ŠคํŠธ ๊ฒ€์ƒ‰์— ๋”ํ•ด์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋‹ค์–‘ํ•œ ์ปฌ๋Ÿผ์œผ๋กœ ์กฐ๊ฑด์„ ๊ฑธ์–ด๋„ ๊ธฐ๊น”๋‚˜๊ฒŒ ์ตœ์ ํ™”๋ฅผ ์ž˜ ํ•ด์ฃผ๋Š”๋ฐ, PostgreSQL์—์„œ๋Š” ๊ทธ ์ •๋„๋ฅผ ๊ธฐ๋Œ€ํ•˜๊ธด ์–ด๋ ต๋‹ค.
BTree ๋”ฐ๋กœ ๋งŒ๋“ค์–ด๋‘๋ฉด Bitmap ์ตœ์ ํ™”๋ฅผ ํ•˜๊ธด ํ•˜๋Š”๋ฐ, Elasticsearch์— ๋น„ํ•˜๋ฉด ์„ฑ๋Šฅ ์ˆ˜์ค€์ด ๋งŽ์ด ์ณ์ง„๋‹ค.


5. Tokenizer ์ง€์› ๋ถ€์‹ค

์˜์–ด ์ •๋„๊นŒ์ง€๋Š” ๋‚ด์žฅ ์—”์ง„์ด๋‚˜ ๊ธฐ๋ณธ ํ™•์žฅ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ ์ด์ƒ์€ ์•ˆ๋œ๋‹ค. ํ•œ๊ตญ์–ด๊ฐ™์€๊ฑด ํฌ๊ธฐํ•˜๋Š”๊ฒŒ ๋งž๋‹ค. ์•„์˜ˆ ๋ถˆ๊ฐ€๋Šฅํ•œ๊ฑด ์•„๋‹ˆ๊ณ , ๋‹ค๊ตญ์–ด์— ๋Œ€ํ•œ ์ง€์›์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฐ”๊นฅ์—์„œ ๋ถ„ํ•ดํ•ด์„œ ์ง์ ‘ ๋„ฃ์œผ๋ฉด ๋˜๊ธด ํ•œ๋‹ค.


6. ๋‹จ์–ด ๋“ฑ์žฅ ํšŸ์ˆ˜๋Š” ์ฒ˜๋ฆฌ ๋ถˆ๊ฐ€

tsquery๋Š” ๊ธฐ๋Šฅ์ด ์ข€ ์ œํ•œ์ ์ด๋‹ค. ํŠน์ • ๋‹จ์–ด๊ฐ€ ํฌํ•จ๋˜๋Š”์ง€ ์—ฌ๋ถ€๋‚˜, ๋А์Šจํ•œ ์Šค์ฝ”์–ด ์ •๋„๋Š” ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ์–ด๋„ ํŠน์ • ๋‹จ์–ด๊ฐ€ ๋ช‡๋ฒˆ ๋“ฑ์žฅํ•˜๋Š”์ง€๋ฅผ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ์กฐ๊ฑด์œผ๋กœ ๊ฑฐ๋Š”๊ฑด ์–ด๋ ต๋‹ค.



์ฐธ์กฐ
https://www.postgresql.org/docs/current/textsearch.html
https://www.postgresql.org/docs/current/textsearch-controls.html
https://www.postgresql.org/docs/current/textsearch-tables.html
https://www.postgresql.org/docs/current/textsearch-dictionaries.html
https://www.postgresql.org/docs/current/textsearch-indexes.html
https://www.postgresql.org/docs/current/datatype-textsearch.html
https://www.postgresql.org/docs/current/textsearch-intro.html