๐Ÿ”ค SQLite FTS5๋กœ ํ•œ๊ธ€ ๋‘ ๊ธ€์ž๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ฉด 0๊ฑด์ด ๋‚˜์˜จ๋‹ค: 2-gram ์ƒ‰์ธ๊ณผ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ˆœ์œ„ ์„ค๊ณ„

@leekh8 ยท September 29, 2026 ยท 12 min read

๋ฉ”๋ชจ๊ฐ€ 663๊ฐœ๊นŒ์ง€ ๋Š˜์–ด๋‚˜์ž ํ•„์š”ํ•œ ๋‚ด์šฉ์„ ์ฐพ๋Š” ๋ฐ ๋“œ๋Š” ์‹œ๊ฐ„์ด ์ ๋Š” ์‹œ๊ฐ„๋ณด๋‹ค ๊ธธ์–ด์กŒ๋‹ค. ์ „๋ฌธ๊ฒ€์ƒ‰๊ธฐ๋ฅผ ๋ถ™์ด๊ธฐ๋กœ ํ•˜๊ณ  SQLite FTS5๋ฅผ ๊ณจ๋ž๋‹ค. ํŒŒ์ผ ํ•˜๋‚˜์— ๋“ค์–ด๊ฐ€๊ณ  ์˜์กด์„ฑ์ด ์—†๋‹ค.

์ฒซ ์งˆ์˜์—์„œ ๋ง‰ํ˜”๋‹ค.

$ sqlite3 index.db "SELECT count(*) FROM t WHERE t MATCH '์šฉ๋Ÿ‰'"
0

์ƒ‰์ธ๋œ ๋ฌธ์„œ์— ๋ฐ์ดํ„ฐ์Šคํ† ์–ด ์šฉ๋Ÿ‰์„ ์ฆ์„คํ–ˆ๋‹ค๊ฐ€ ๋ถ„๋ช…ํžˆ ๋“ค์–ด ์žˆ๋Š”๋ฐ 0๊ฑด์ด๋‹ค. ์ฆ์„ค๋„ 0๊ฑด์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ฐ์ดํ„ฐ์Šคํ† ์–ด๋Š” 1๊ฑด์ด ๋‚˜์˜จ๋‹ค.

์ด ๊ธ€์€ ๊ทธ 0๊ฑด์—์„œ ์ถœ๋ฐœํ•ด ์“ธ ๋งŒํ•œ ํ•œ๊ตญ์–ด ๊ฒ€์ƒ‰๊ธฐ๊ฐ€ ๋˜๊ธฐ๊นŒ์ง€ ๋ฐŸ์€ ํ•จ์ •์„ ์ •๋ฆฌํ•œ ๊ฒƒ์ด๋‹ค. ๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ํ† ํฌ๋‚˜์ด์ €๋ฅผ ๊ณ ์น˜๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚˜์ง€ ์•Š๋Š”๋‹ค. ์ƒ‰์ธ, ์งˆ์˜, ์ˆœ์œ„ ์œตํ•ฉ ์„ธ ์ธต์—์„œ ๊ฐ๊ฐ ๋‹ค๋ฅด๊ฒŒ ๊นจ์ง„๋‹ค.

๊ธฐ๋ณธ ํ† ํฌ๋‚˜์ด์ € ๋‘ ๊ฐœ๊ฐ€ ๋ชจ๋‘ ํ•œ๊ตญ์–ด๋ฅผ ๋ชป ์“ด๋‹ค

SQLite 3.49.1์—์„œ ๊ฐ™์€ ๋ฌธ์„œ ํ•˜๋‚˜๋ฅผ ์„ธ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ์ƒ‰์ธํ•˜๊ณ  ๊ฐ™์€ ์งˆ์˜๋ฅผ ๋˜์กŒ๋‹ค.

์งˆ์˜ unicode61 trigram 2-gram ์ „์ฒ˜๋ฆฌ
์šฉ๋Ÿ‰ 0๊ฑด 0๊ฑด 1๊ฑด
์ฆ์„ค 0๊ฑด 0๊ฑด 1๊ฑด
๋ฐ์ดํ„ฐ์Šคํ† ์–ด 1๊ฑด 1๊ฑด 1๊ฑด

์ด์œ ๋Š” ๊ฐ๊ฐ ๋‹ค๋ฅด๋‹ค.

unicode61์€ ๊ณต๋ฐฑ๊ณผ ๋ฌธ์žฅ๋ถ€ํ˜ธ๋กœ๋งŒ ์ž๋ฅธ๋‹ค.fts5-tokenizers ๋ฌธ์„œ์˜ ํ† ํฐ์€ ๋ฐ์ดํ„ฐ์Šคํ† ์–ด, ์šฉ๋Ÿ‰์„, ์ฆ์„คํ–ˆ๋‹ค ์„ธ ๊ฐœ๋‹ค. ํ•œ๊ตญ์–ด๋Š” ์กฐ์‚ฌ์™€ ์–ด๋ฏธ๊ฐ€ ๋‚ฑ๋ง์— ๋ถ™์–ด ๋ฒ„๋ฆฌ๋ฏ€๋กœ ์šฉ๋Ÿ‰์€ ์–ด๋–ค ํ† ํฐ๊ณผ๋„ ๊ฐ™์ง€ ์•Š๋‹ค. ๋ฐ์ดํ„ฐ์Šคํ† ์–ด๋งŒ ๊ฑธ๋ฆฐ ์ด์œ ๋Š” ๊ทธ ๋‚ฑ๋ง ๋’ค์— ์กฐ์‚ฌ๊ฐ€ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์šฐ์—ฐ์ด๋‹ค.

trigram์€ ์—ฐ์†ํ•œ ์„ธ ๊ธ€์ž๋ฅผ ํ•˜๋‚˜์˜ ํ† ํฐ์œผ๋กœ ๋ณธ๋‹ค.fts5-tokenizers ๋ถ€๋ถ„ ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰์ด ๋˜๋‹ˆ ์กฐ์‚ฌ ๋ฌธ์ œ๋Š” ํ’€๋ฆฐ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋‘ ๊ธ€์ž ์งˆ์˜๋Š” ํ† ํฐ์ด ์•„์˜ˆ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š์•„ 0๊ฑด์ด๋‹ค. ํ•œ๊ตญ์–ด ์‹ค๋ฌด ๋‚ฑ๋ง์€ ๋‘ ๊ธ€์ž๊ฐ€ ๋Œ€๋ถ€๋ถ„์ด๋‹ค. ์šฉ๋Ÿ‰, ์ฆ์„ค, ํ™•์žฅ, ์žฅ์• , ์ ‘์†, ๊ถŒํ•œ, ๋ฐฑ์—…, ๋ณต๊ตฌ. ๊ฐ€์žฅ ์ž์ฃผ ์น˜๋Š” ์งˆ์˜๊ฐ€ ์ „๋ถ€ ๋ง‰ํžŒ๋‹ค.

โš ๏ธ ์˜์–ด๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด ๋‘˜ ๋‹ค ๋ฉ€์ฉกํ•ด ๋ณด์ธ๋‹ค. capacity๋Š” unicode61์—์„œ ์ž˜ ๊ฑธ๋ฆฌ๊ณ  trigram์—์„œ๋„ ์„ธ ๊ธ€์ž๋ฅผ ๋„˜๋Š”๋‹ค. ํ•œ๊ตญ์–ด๋ฅผ ๋„ฃ์–ด ๋ณด๊ธฐ ์ „๊นŒ์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š”๋‹ค.

์ƒ‰์ธ ์ธต: ํ•œ๊ธ€ ๊ตฌ๊ฐ„๋งŒ ๊ฒน์น˜๋Š” 2-gram์œผ๋กœ ์ชผ๊ฐ ๋‹ค

ํ•ด๋ฒ•์€ ์ƒ‰์ธ์— ๋„ฃ๊ธฐ ์ „์— ํ•œ๊ธ€ ๊ตฌ๊ฐ„๋งŒ ๊ฒน์น˜๋Š” 2-gram์œผ๋กœ ๋ฏธ๋ฆฌ ์ชผ๊ฐœ๋Š” ๊ฒƒ์ด๋‹ค. ํ† ํฌ๋‚˜์ด์ €๋Š” unicode61์„ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค.

import re

HANGUL = re.compile(r"[๊ฐ€-ํžฃ]+")

def ngramize(text: str) -> str:
    """ํ•œ๊ธ€ ๊ตฌ๊ฐ„๋งŒ ๊ฒน์น˜๋Š” 2-gram์œผ๋กœ ์ชผ๊ฐ ๋‹ค. ์˜๋ฌธ๊ณผ ์ˆซ์ž๋Š” unicode61์ด ์•Œ์•„์„œ ์ž๋ฅธ๋‹ค."""
    out, pos = [], 0
    for m in HANGUL.finditer(text):
        out.append(text[pos:m.start()])
        w = m.group()
        if len(w) == 1:
            out.append(f" {w} ")
        else:
            out.append(" " + " ".join(w[i:i + 2] for i in range(len(w) - 1)) + " ")
        pos = m.end()
    out.append(text[pos:])
    return "".join(out)
"๋ฐ์ดํ„ฐ์Šคํ† ์–ด ์šฉ๋Ÿ‰์„ ์ฆ์„คํ–ˆ๋‹ค"
  -> "๋ฐ์ด ์ดํ„ฐ ํ„ฐ์Šค ์Šคํ†  ํ† ์–ด  ์šฉ๋Ÿ‰ ๋Ÿ‰์„  ์ฆ์„ค ์„คํ–ˆ ํ–ˆ๋‹ค"

๋‘ ๊ฐ€์ง€๊ฐ€ ์˜๋„์ ์ด๋‹ค.

์˜๋ฌธ๊ณผ ์ˆซ์ž๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. unicode61์ด ์ด๋ฏธ ์ œ๋Œ€๋กœ ์ž๋ฅด๊ณ  ์žˆ๋Š” ์˜์—ญ์ด๋‹ค. ์ „๋ถ€ 2-gram์œผ๋กœ ๋ฐ€์–ด ๋„ฃ์œผ๋ฉด rocky๊ฐ€ ro oc ck ky๊ฐ€ ๋˜์–ด ์ƒ‰์ธ๋งŒ ์ปค์ง€๊ณ  ์ •ํ™•๋„๋Š” ๋–จ์–ด์ง„๋‹ค. ์ •๊ทœ์‹์œผ๋กœ ํ•œ๊ธ€ ๊ตฌ๊ฐ„๋งŒ ๊ณจ๋ผ๋‚ด๋Š” ์ด์œ ๊ฐ€ ์ด๊ฒƒ์ด๋‹ค.

๊ฒน์ณ์„œ ์ž๋ฅธ๋‹ค. ์šฉ๋Ÿ‰์„์„ ์šฉ๋Ÿ‰ / ๋Ÿ‰์„๋กœ ๋งŒ๋“ค์–ด์•ผ ์šฉ๋Ÿ‰์œผ๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ฒน์น˜์ง€ ์•Š๊ฒŒ ์ž๋ฅด๋ฉด(์šฉ๋Ÿ‰ / ์„) ๊ฒฝ๊ณ„๊ฐ€ ์–ด๊ธ‹๋‚œ ์งˆ์˜๊ฐ€ ์ „๋ถ€ ๋น—๋‚˜๊ฐ„๋‹ค.

์งˆ์˜์—๋„ ๊ฐ™์€ ๋ณ€ํ™˜์„ ์ ์šฉํ•œ๋‹ค. ์ƒ‰์ธ๊ณผ ์งˆ์˜๊ฐ€ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ํ†ต๊ณผํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ณ€ํ™˜๊ธฐ๋ฅผ ํ•œ ๊ณณ์— ๋‘๊ณ  ์–‘์ชฝ์—์„œ ๋ถ€๋ฅธ๋‹ค.

์งˆ์˜ ์ธต: ์กฐ๊ฐ์„ AND๋กœ ํ’€๋ฉด ๋‹ค๋ฅธ ๋ฌธ์„œ๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค

์—ฌ๊ธฐ์„œ ๋‘ ๋ฒˆ์งธ ํ•จ์ •์ด ๋‚˜์˜จ๋‹ค. ๋ฐ์ดํ„ฐ์Šคํ† ์–ด๋ฅผ 2-gram์œผ๋กœ ์ชผ๊ฐœ๋ฉด ์กฐ๊ฐ์ด ๋‹ค์„ฏ ๊ฐœ๋‹ค. ์ด๊ฑธ ๊ทธ๋Œ€๋กœ ๋˜์ง€๋ฉด FTS5๋Š” ์•”๋ฌต์  AND๋กœ ํ•ด์„ํ•œ๋‹ค.fts5-phrases

๋ฐ์ด AND ์ดํ„ฐ AND ํ„ฐ์Šค AND ์Šคํ†  AND ํ† ์–ด

๋‹ค์„ฏ ์กฐ๊ฐ์ด ํ•œ ๋ฌธ์„œ ์•ˆ์— ๋‹ค ์žˆ๊ธฐ๋งŒ ํ•˜๋ฉด ๊ฑธ๋ฆฐ๋‹ค. ์„œ๋กœ ๋ถ™์–ด ์žˆ์„ ํ•„์š”๊ฐ€ ์—†๋‹ค. ์‹ค์ธก์œผ๋กœ ํ™•์ธํ–ˆ๋‹ค.

[๋ฌธ์„œ] ๋ฐ์ดํ„ฐ ์„ผํ„ฐ์Šค ์Šคํ† ์–ด ์ด์•ผ๊ธฐ
  gram: ๋ฐ์ด ์ดํ„ฐ ์„ผํ„ฐ ํ„ฐ์Šค ์Šคํ†  ํ† ์–ด ์ด์•ผ ์•ผ๊ธฐ

  ๋ฐ์ด AND ์ดํ„ฐ AND ํ„ฐ์Šค AND ์Šคํ†  AND ํ† ์–ด   -> 1๊ฑด   โŒ ๊ฑธ๋ฆฌ๋ฉด ์•ˆ ๋˜๋Š” ๋ฌธ์„œ
  "๋ฐ์ด ์ดํ„ฐ ํ„ฐ์Šค ์Šคํ†  ํ† ์–ด"                 -> 0๊ฑด   โœ…

๋ฐ์ดํ„ฐ์Šคํ† ์–ด๋ผ๋Š” ๋‚ฑ๋ง์ด ์—†๋Š” ๋ฌธ์„œ๊ฐ€ ๊ฑธ๋ ธ๋‹ค. ์กฐ๊ฐ๋“ค์ด ์„ธ ๋‚ฑ๋ง์— ํฉ์–ด์ ธ ์žˆ์—ˆ์„ ๋ฟ์ด๋‹ค.

ํ•œ ๋‚ฑ๋ง์—์„œ ๋‚˜์˜จ ์กฐ๊ฐ๋“ค์€ ํ•˜๋‚˜์˜ ์ธ์ ‘ phrase๋กœ ๋ฌถ์–ด์•ผ ํ•œ๋‹ค. ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ๋ฉด FTS5๋Š” ๊ทธ ์ˆœ์„œ ๊ทธ๋Œ€๋กœ ์ด์–ด์ ธ ์žˆ๋Š” ๋ฌธ์„œ๋งŒ ๋Œ๋ ค์ค€๋‹ค.fts5-phrases

def gram_phrase(*parts: str) -> str:
    """์กฐ๊ฐ๋“ค์˜ gram์„ ์ด์–ด ํ•˜๋‚˜์˜ ์ธ์ ‘ phrase๋กœ ๋งŒ๋“ ๋‹ค."""
    g = [x for p in parts for x in grams(p)]
    return '"' + " ".join(g) + '"' if g else ""

์ธ์ ‘ ์กฐ๊ฑด์„ NEAR๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค

์ธ์ ‘์„ ๋‚˜ํƒ€๋‚ด๋ ค๊ณ  NEAR๋ฅผ ๋– ์˜ฌ๋ฆฌ๊ธฐ ์‰ฌ์šด๋ฐ ์ด๊ฑด ํ‹€๋ฆฐ ๋„๊ตฌ๋‹ค. NEAR๋Š” ๊ฑฐ๋ฆฌ๋งŒ ๋ณด๊ณ  ์ˆœ์„œ๋ฅผ ๋”ฐ์ง€์ง€ ์•Š๋Š”๋‹ค.fts5-near

[๋ฌธ์„œ] ์Šคํ† ์–ด๋ฐ์ดํ„ฐ ์ •๋ฆฌ

  NEAR("์Šคํ†  ํ† ์–ด" "๋ฐ์ด ์ดํ„ฐ", 3)   -> 1๊ฑด   โŒ
  "๋ฐ์ด ์ดํ„ฐ ํ„ฐ์Šค ์Šคํ†  ํ† ์–ด"          -> 0๊ฑด   โœ…

์Šคํ† ์–ด๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ์ดํ„ฐ์Šคํ† ์–ด ์งˆ์˜์— ๊ฑธ๋ ธ๋‹ค. ์กฐ๊ฐ์„ ์ด์–ด ๋ถ™์ธ phrase๋Š” ์ˆœ์„œ๊ฐ€ ๋ณด์กด๋˜๋ฏ€๋กœ ์ด ์˜คํƒ์ด ์›์ฒœ์ ์œผ๋กœ ์ƒ๊ธฐ์ง€ ์•Š๋Š”๋‹ค.

2-gram์€ ๋„์–ด์“ฐ๊ธฐ์— ํ•œ์ชฝ์œผ๋กœ๋งŒ ๋šซ๋ ค ์žˆ๋‹ค

์„ธ ๋ฒˆ์งธ ํ•จ์ •์€ ํ•œ๊ตญ์–ด ํŠน์œ ์˜ ๊ฒƒ์ด๋‹ค. ๊ฐ™์€ ๋‚ฑ๋ง์„ ์–ด๋–ค ์‚ฌ๋žŒ์€ ๋ถ™์—ฌ ์“ฐ๊ณ  ์–ด๋–ค ์‚ฌ๋žŒ์€ ๋„์–ด ์“ด๋‹ค.

[๋ฌธ์„œ] ๋ฐฑ์—… ์„œ๋ฒ„ ์ด์ค‘ํ™”
  gram: ๋ฐฑ์—… ์„œ๋ฒ„ ์ด์ค‘ ์ค‘ํ™”

  "๋ฐฑ์—… ์—…์„œ ์„œ๋ฒ„"   -> 0๊ฑด   โ† ๋ถ™์—ฌ ์“ด ์งˆ์˜ "๋ฐฑ์—…์„œ๋ฒ„"
  "๋ฐฑ์—… ์„œ๋ฒ„"        -> 1๊ฑด   โ† ๋‘ ์กฐ๊ฐ์œผ๋กœ ๊ฐ€๋ฅธ ํ‘œ๊ธฐ

๋ฐฑ์—…์„œ๋ฒ„์˜ ์กฐ๊ฐ์€ ๋ฐฑ์—…/์—…์„œ/์„œ๋ฒ„์ธ๋ฐ, ๋ฌธ์„œ์— ๋ฐฑ์—… ์„œ๋ฒ„๋ผ๊ณ  ์ ํ˜€ ์žˆ์œผ๋ฉด ๊ฐ€์šด๋ฐ ์—…์„œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ณต๋ฐฑ์ด ๋‘ ๊ธ€์ž์˜ ์—ฐ๊ฒฐ์„ ๋Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋šซ๋ ค ์žˆ๋Š” ๋ฐฉํ–ฅ์€ ํ•œ์ชฝ๋ฟ์ด๋‹ค. ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ(๋„์–ด ์“ด ์งˆ์˜์—์„œ ๋ถ™์—ฌ ์“ด ๋ฌธ์„œ๋ฅผ ์ฐพ๋Š” ๊ฒฝ์šฐ)์€ ๋ฐฑ์—…๊ณผ ์„œ๋ฒ„๊ฐ€ ๊ฐ๊ฐ ๋ฌธ์„œ์˜ ์กฐ๊ฐ์— ๋“ค์–ด ์žˆ์œผ๋ฏ€๋กœ ๋‚ฑ๋ง AND๋กœ ์ด๋ฏธ ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ๋ถ™์—ฌ ์“ด ์งˆ์˜๋งŒ ๋ณด์™„ํ•˜๋ฉด ๋œ๋‹ค. ๋‚ฑ๋ง์„ ๋‘ ์กฐ๊ฐ์œผ๋กœ ๊ฐ€๋ฅธ ํ‘œ๊ธฐ๋„ phrase๋กœ ๋งŒ๋“ค์–ด OR๋กœ ํ•จ๊ป˜ ๋˜์ง„๋‹ค.

๋ฐฑ์—…์„œ๋ฒ„  ->  "๋ฐฑ์—… ์—…์„œ ์„œ๋ฒ„"  OR  "๋ฐฑ์—… ์„œ๋ฒ„"

๊ฐ€๋ฅด๋Š” ์œ„์น˜๋Š” ์ „๋ถ€ ์‹œ๋„ํ•˜๋˜ ์ƒํ•œ์„ ๋‘”๋‹ค. ์—ฌ๋Ÿ ๊ธ€์ž๋ฅผ ๋„˜๋Š” ๋‚ฑ๋ง์€ ์กฐํ•ฉ๋งŒ ๋Š˜๊ณ  ์‹ค์ œ๋กœ ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์ด ์—†๋‹ค.

๋‚ฑ๋ง ์ „๋ถ€ AND๋Š” ํ‹€๋ฆฐ ๊ธฐ๋ณธ๊ฐ’์ด๋‹ค

์—ฌ๊ธฐ๊นŒ์ง€ ์˜ค๋ฉด ๋‚ฑ๋ง ํ•˜๋‚˜๋Š” ์ •ํ™•ํžˆ ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์‚ฌ๋žŒ์€ ๋‚ฑ๋ง ํ•˜๋‚˜๋ฅผ ์น˜์ง€ ์•Š๋Š”๋‹ค.

$ vs "vmware ์šฉ๋Ÿ‰ ์ฆ์„ค"
0๊ฑด

์„ธ ๋‚ฑ๋ง ๋ชจ๋‘ ๋ฉ”๋ชจ ์–ด๋”˜๊ฐ€์— ์žˆ๋‹ค. ํ•œ ์„น์…˜ ์•ˆ์— ์…‹์ด ๊ฐ™์ด ์žˆ๋Š” ๊ณณ์ด ์—†์—ˆ์„ ๋ฟ์ด๋‹ค. ์‚ฌ๋žŒ์€ ์ž์—ฐ์Šค๋Ÿฌ์šด ์–ด๊ตฌ๋ฅผ ๋˜์ง€๋Š”๋ฐ AND๋Š” ํ•˜๋‚˜๋งŒ ๋น—๋‚˜๊ฐ€๋ฉด 0๊ฑด์„ ์ค€๋‹ค.

๊ทธ๋ ‡๋‹ค๊ณ  OR์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋‘๋ฉด ํ”ํ•œ ๋‚ฑ๋ง ํ•˜๋‚˜์— ๊ฑธ๋ฆฐ ์žก์Œ์ด ์ƒ์œ„๋ฅผ ๋จน๋Š”๋‹ค. ๋‘ ๋‹จ๊ณ„๋กœ ๊ฐ„๋‹ค.

rows = fetch(" AND ".join(phrases), limit * 25)      # ์ „๋ถ€ ๊ฑธ๋ฆฐ ๊ฒƒ ๋จผ์ €
if len(rows) < limit:
    rows += fetch(" OR ".join(phrases), 4000)        # ๋ชจ์ž๋ผ๋ฉด ๋„“ํžŒ๋‹ค

๋„“ํžŒ ๋’ค์—๋Š” ์‹ค์ œ ์ ์ค‘ ๋‚ฑ๋ง ์ˆ˜๋กœ ์ค„์„ ์„ธ์šด๋‹ค. ์—ฌ๊ธฐ์— ํ•œ ๋ฒˆ ๋” ํ•จ์ •์ด ์žˆ๋‹ค.

์ ์ค‘ ์ˆ˜๋ฅผ ์ •๋ ฌ ํ‚ค๋กœ ๋”ฐ๋กœ ์“ฐ๋ฉด ๋‹ค๋ฅธ ๊ฐ€์ค‘์น˜๊ฐ€ ์˜์˜ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.

sort(key=(์ ์ค‘์ˆ˜, ์ ์ˆ˜))๋กœ ์“ฐ๋ฉด ์ ์ค‘ ์ˆ˜๊ฐ€ ๊ฐ™์€ ๊ฒƒ๋ผ๋ฆฌ๋งŒ ์ ์ˆ˜ ๋น„๊ต๊ฐ€ ์ผ์–ด๋‚œ๋‹ค. ์ ์ค‘ ์ˆ˜๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ๋†’์œผ๋ฉด ๋‹ค๋ฅธ ๋ชจ๋“  ๋ณด์ •์„ ์ด๊ธด๋‹ค. ์ ์ค‘ ์ˆ˜๋Š” ์ •๋ ฌ ํ‚ค๊ฐ€ ์•„๋‹ˆ๋ผ ์ ์ˆ˜์— ์ ‘์–ด ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

score -= 4.0 * coverage      # bm25๋Š” ์ž‘์„์ˆ˜๋ก ์ƒ์œ„๋ผ, ๋นผ๋Š” ๊ฒƒ์ด ๊ฐ€์‚ฐ์ ์ด๋‹ค

bm25()๋Š” ๋‚ฎ์„์ˆ˜๋ก ์ข‹์€ ์ ์ˆ˜๋‹ค.fts5-bm25 ๋ถ€ํ˜ธ๋ฅผ ๋ฐ˜๋Œ€๋กœ ์žก์œผ๋ฉด ๊ฐ€์žฅ ๊ด€๋ จ ์—†๋Š” ๊ฒฐ๊ณผ๊ฐ€ 1๋“ฑ์œผ๋กœ ์˜ฌ๋ผ์˜ค๋Š”๋ฐ, ๊ฒฐ๊ณผ๊ฐ€ ๊ทธ๋Ÿด๋“ฏํ•ด์„œ ํ•œ์ฐธ ๋’ค์—์•ผ ์•Œ์•„์ฐจ๋ฆฐ๋‹ค.

์˜๋ฏธ ๊ฒ€์ƒ‰์„ ์žฌ์ •๋ ฌ๋กœ ๋งŒ๋“ค๋ฉด ๋ชป ์ฐพ๋Š” ๋ฌธ์„œ๊ฐ€ ์ƒ๊ธด๋‹ค

ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰์ด ์™„์„ฑ๋ผ๋„ ๋‚จ๋Š” ๊ตฌ๋ฉ์ด ์žˆ๋‹ค.

"๋””์Šคํฌ ๋Š˜๋ฆฌ๊ธฐ" ๋กœ ๊ฒ€์ƒ‰  ->  ใ€Œ๋””์Šคํฌ ์šฉ๋Ÿ‰ ์ฆ์„คใ€ ์„น์…˜์ด ์•ˆ ๋‚˜์˜จ๋‹ค

๋Š˜๋ฆฌ๊ธฐ์™€ ์ฆ์„ค์€ ํ•œ ๊ธ€์ž๋„ ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค. ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ์„ ๋ถ™์ผ ์ฐจ๋ก€๋‹ค.e5

์—ฌ๊ธฐ์„œ ๋Œ€๋ถ€๋ถ„์ด ํƒํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ์žฌ์ •๋ ฌ(reranking) ์ด๋‹ค. ํ‚ค์›Œ๋“œ๋กœ ํ›„๋ณด 200๊ฐœ๋ฅผ ๋ฝ‘๊ณ  ๊ทธ ์•ˆ์—์„œ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„๋กœ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พผ๋‹ค. ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฅด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์œ„ ๋ฌธ์ œ๋ฅผ ์ „ํ˜€ ํ’€์ง€ ๋ชปํ•œ๋‹ค. ใ€Œ๋””์Šคํฌ ์šฉ๋Ÿ‰ ์ฆ์„คใ€ ์„น์…˜์€ ์• ์ดˆ์— ํ‚ค์›Œ๋“œ ํ›„๋ณด 200๊ฐœ์— ๋“ค์–ด์˜ค์ง€ ์•Š๋Š”๋‹ค. ํ›„๋ณด์— ์—†๋Š” ๊ฒƒ์€ ์žฌ์ •๋ ฌํ•ด๋„ ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค.

์žฌ์ •๋ ฌ์€ ๊ฒ€์ƒ‰์ด ์•„๋‹ˆ๋ผ ์ •๋ ฌ์ด๋‹ค. ๊ฒ€์ƒ‰ ๋ฒ”์œ„๋ฅผ ๋„“ํžˆ๋Š” ์ผ์€ ํ›„๋ณด๋ฅผ ๋งŒ๋“œ๋Š” ๋‹จ๊ณ„์—์„œ๋งŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•„์š”ํ•œ ๊ฒƒ์€ ํ•ฉ์ง‘ํ•ฉ์ด๋‹ค. ํ‚ค์›Œ๋“œ ์ƒ์œ„ N๊ณผ ๋ฒกํ„ฐ ์ „๋Ÿ‰ ๋น„๊ต ์ƒ์œ„ M์„ ๊ฐ๊ฐ ๋งŒ๋“ค์–ด ํ•ฉ์นœ๋‹ค. 26,000ํ–‰์— 384์ฐจ์›์ด๋ฉด ํ–‰๋ ฌ๊ณฑ์ด ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋ผ ์ „๋Ÿ‰ ๋น„๊ต๋ฅผ ํ”ผํ•  ์ด์œ ๊ฐ€ ์—†๋‹ค.

sims = M @ qv          # ๋‘˜ ๋‹ค ์ •๊ทœํ™”๋ผ ์žˆ์œผ๋ฏ€๋กœ ๋‚ด์ ์ด ๊ณง ์ฝ”์‚ฌ์ธ

bm25 ์ ์ˆ˜์™€ ์ฝ”์‚ฌ์ธ์€ ๋ˆˆ๊ธˆ์ด ๋‹ฌ๋ผ ์ง์ ‘ ๋”ํ•  ์ˆ˜ ์—†๋‹ค. ์ˆœ์œ„๋งŒ ์“ฐ๋Š” RRF(Reciprocal Rank Fusion)๋กœ ํ•ฉ์นœ๋‹ค.rrf

rrf = 1.0 / (60 + kw_rank) + alpha / (60 + sem_rank)

RRF๋งŒ์œผ๋กœ ์ค„ ์„ธ์šฐ๋ฉด ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ€๋ ค๋‚œ๋‹ค

ํ•ฉ์ง‘ํ•ฉ๊นŒ์ง€ ๋งŒ๋“ค์–ด ๋†“๊ณ ๋„ ์œตํ•ฉ์—์„œ ๋‹ค์‹œ ์ง„๋‹ค.

๋ฐฑ์—…์„œ๋ฒ„๋กœ ๊ฒ€์ƒ‰ํ–ˆ๋”๋‹ˆ ์ œ๋ชฉ์— ๊ทธ ๋‚ฑ๋ง์ด ๊ทธ๋Œ€๋กœ ๋ฐ•ํžŒ ๋ฌธ์„œ๊ฐ€ 10์œ„ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ”๋‹ค. ํ‚ค์›Œ๋“œ ๋‹จ๋…์œผ๋กœ๋Š” 1์œ„์™€ 2์œ„์˜€๋˜ ๋ฌธ์„œ๋‹ค. ์‚ฌ์šฉ์ž ๋ˆˆ์—๋Š” "๋ฐฉ๊ธˆ ๋งŒ๋“  ํŒŒ์ผ์ด ํ•œ์ฐธ ์•„๋ž˜ ์žˆ๋‹ค"๋กœ ๋ณด์ธ๋‹ค.

RRF๋Š” ์ˆœ์œ„๋งŒ ๋ณด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์‚ฐ์ˆ˜๋ฅผ ํ•ด ๋ณด๋ฉด ๋ฐ”๋กœ ๋“œ๋Ÿฌ๋‚œ๋‹ค.

๋ฌธ์„œ ํ‚ค์›Œ๋“œ ์ˆœ์œ„ ์˜๋ฏธ ์ˆœ์œ„ RRF ์ ์ˆ˜
์ œ๋ชฉ์ด ์ •ํ™•ํžˆ ์ผ์น˜ 1์œ„ ํ›„๋ณด์— ์—†์Œ 1/60 = 0.0167
๊ทธ๋Ÿด๋“ฏํ•˜๊ฒŒ ๊ด€๋ จ๋จ 21์œ„ 4์œ„ 1/80 + 1/63 = 0.0284

๋‘ ์ธต ๋ชจ๋‘์—์„œ ์–ด์ค‘๊ฐ„ํ•œ ๋ฌธ์„œ๊ฐ€, ํ•œ ์ธต์—์„œ 1๋“ฑ์ธ ๋ฌธ์„œ๋ฅผ ์ด๊ธด๋‹ค. RRF์˜ ์„ค๊ณ„๊ฐ€ ์›๋ž˜ ๊ทธ๋ ‡๋‹ค. ์—ฌ๋Ÿฌ ๋žญ์ปค์˜ ํ•ฉ์˜๋ฅผ ์ค‘์‹œํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋ฏ€๋กœ ํ•œ์ชฝ์—๋งŒ ๋‚˜ํƒ€๋‚œ ๊ฒฐ๊ณผ๋Š” ๊ตฌ์กฐ์ ์œผ๋กœ ๋ถˆ๋ฆฌํ•˜๋‹ค.

์˜๋ฏธ ์ชฝ ๋น„์ค‘(alpha)์„ ๋‚ฎ์ถ”๋Š” ๊ฒƒ์œผ๋กœ๋Š” ์•ˆ ํ’€๋ฆฐ๋‹ค. ์œ„ ์ˆ˜์น˜๋กœ ๊ณ„์‚ฐํ•ด ๋ณด๋ฉด 0.3 ์•„๋ž˜๊นŒ์ง€ ๋‚ด๋ ค์•ผ ๊ฒจ์šฐ ๋™๋ฅ ์ธ๋ฐ, ๊ฑฐ๊ธฐ๊นŒ์ง€ ๋‚ด๋ฆฌ๋ฉด ์˜๋ฏธ ์ธต์„ ์ผ  ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง„๋‹ค.

ํ•ด๋ฒ•์€ ์œตํ•ฉ ์ „์— ๋ฌถ์Œ์„ ๊ฐ€๋ฅด๋Š” ๊ฒƒ์ด๋‹ค.

# ์งˆ์˜ ๋‚ฑ๋ง์ด ์ „๋ถ€ ๊ฑธ๋ฆฐ ๊ฒƒ๊ณผ ์•„๋‹Œ ๊ฒƒ์„ ๋จผ์ € ๊ฐ€๋ฅธ๋‹ค
r["exact"] = 0 if r["cov"] >= len(terms) else 1
out = sorted(merged.values(), key=lambda x: (x["exact"], -x["rrf"]))

๋‚ฑ๋ง์ด ์ „๋ถ€ ๊ฑธ๋ฆฐ ๊ฒƒ์„ ์•ž ๋ฌถ์Œ์— ๋†“๊ณ  ๊ทธ ์•ˆ์—์„œ๋งŒ RRF๋ฅผ ์“ด๋‹ค. ๋‚ฑ๋ง์ด ํ•˜๋‚˜๋„ ์•ˆ ๊ฒน์น˜๋Š” ์ˆœ์ˆ˜ ์˜๋ฏธ ๊ฒฐ๊ณผ๋Š” ๋’ท ๋ฌถ์Œ์— ๊ทธ๋Œ€๋กœ ์‚ด์•„ ์žˆ๋‹ค. ์ •ํ™•ํžˆ ๋งž์€ ๊ฒƒ์„ ์œ„์— ๋‘๋ฉด์„œ ์˜๋ฏธ ์ธต์˜ ๋ฐœ๊ฒฌ ๋Šฅ๋ ฅ์€ ์žƒ์ง€ ์•Š๋Š”๋‹ค.

์งˆ์˜
ํ•œ๊ธ€ 2-gram ๋ณ€ํ™˜
ํ‚ค์›Œ๋“œ ํ›„๋ณด: AND ๋จผ์ €, ๋ชจ์ž๋ผ๋ฉด OR
์งˆ์˜ ์ž„๋ฒ ๋”ฉ
์˜๋ฏธ ํ›„๋ณด: ์ „๋Ÿ‰ ์ฝ”์‚ฌ์ธ, ๋ฐ”๋‹ฅ์„  ์ด์ƒ๋งŒ
ํ•ฉ์ง‘ํ•ฉ
๋‚ฑ๋ง์ด ์ „๋ถ€ ๊ฑธ๋ฆฐ ๊ฒƒ์„ ์•ž ๋ฌถ์Œ์œผ๋กœ
๋ฌถ์Œ ์•ˆ์—์„œ RRF ์œตํ•ฉ
๋ฌธ์„œ๋‹น 2๊ฑด๊นŒ์ง€ ๋จผ์ € ์ฑ„์›Œ ๋ฐฐ์น˜

๋ฒกํ„ฐ๋Š” ์–ธ์ œ๋‚˜ ๋‹ต์„ ์ค€๋‹ค

์˜๋ฏธ ๊ฒ€์ƒ‰์—๋Š” "๊ฒฐ๊ณผ ์—†์Œ"์ด ์—†๋‹ค. ๋‚ฑ๋ง์ด ํ•˜๋‚˜๋„ ์•ˆ ๊ฒน์ณ๋„ ํ•ญ์ƒ ์ƒ์œ„ K๊ฐœ๋ฅผ ๋Œ๋ ค์ค€๋‹ค. ์กด์žฌํ•˜์ง€์•Š๋Š”๋‚ฑ๋ง ๊ฐ™์€ ์งˆ์˜์—๋„ ๋ฌด์–ธ๊ฐ€ ๋‚˜์˜จ๋‹ค.

์œ ์‚ฌ๋„ ๋ฐ”๋‹ฅ์„ ์ด ํ•„์š”ํ•˜๋‹ค. ์‹ค์ธกํ•ด ๋ณด๋ฉด ๋‹ค๊ตญ์–ด e5 ๊ณ„์—ด์€ ์ฝ”์‚ฌ์ธ์ด 0.85~0.89 ์‚ฌ์ด์— ๋ชฐ๋ ค ์žˆ๋‹ค. ๊ฐ’์˜ ์ ˆ๋Œ€ ํฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ถ„ํฌ๋ฅผ ๋ณด๊ณ  ์ •ํ•ด์•ผ ํ•œ๋‹ค. 0.5 ๊ฐ™์€ ์ƒ์‹์ ์ธ ๊ฐ’์„ ๋„ฃ์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ๊ฑธ๋Ÿฌ์ง€์ง€ ์•Š๋Š”๋‹ค.

๊ฐ€์ค‘์น˜๋Š” ์–‘์ชฝ ์ˆœ์œ„์— ๊ฐ™์ด ๊ฑธ์–ด์•ผ ํ•œ๋‹ค

๊ฐ™์€ ์‚ฌ์‹ค์ด ๋‚ ์งœ๋ณ„ ์ผ์ง€์™€ ์ •๋ฆฌ ๋…ธํŠธ ์–‘์ชฝ์— ์ ํ˜€ ์žˆ์œผ๋ฉด ์ •๋ฆฌ๋ณธ์„ ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค. ์ตœ๊ทผ์— ์ ์€ ๊ฒƒ๋„ ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค. ๋‘˜ ๋‹ค ํ‚ค์›Œ๋“œ ์ ์ˆ˜์— ๊ฐ€์‚ฐ์ ์œผ๋กœ ๋„ฃ์—ˆ๋‹ค.

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ๋ฅผ ์ผœ๋Š” ์ˆœ๊ฐ„ ํšจ๊ณผ๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค. ์˜๋ฏธ ์ชฝ ์ˆœ์œ„์—๋Š” ๋ณด์ •์ด ์—†์œผ๋‹ˆ, ์œตํ•ฉํ•  ๋•Œ ๋ณด์ • ์—†๋Š” ์ˆœ์œ„๊ฐ€ ์ ˆ๋ฐ˜์„ ์ฐจ์ง€ํ•˜๊ณ  ๊ทธ๋งŒํผ ํฌ์„๋œ๋‹ค.

# ์˜๋ฏธ ์ชฝ ์ˆœ์œ„์—๋„ ๊ฐ™์€ ๋ณด์ •์„ ๊ฑด๋‹ค. ์œ ์‚ฌ๋„๊ฐ€ 0.85~0.89์— ๋ชฐ๋ ค ์žˆ์–ด
# 0.01 ๋‹จ์œ„ ๋ณด์ •์œผ๋กœ ์ถฉ๋ถ„ํžˆ ์›€์ง์ธ๋‹ค.
rank = sim - 0.01 * (dir_weight + recency_bonus)

์ผ๋ฐ˜ํ™”ํ•˜๋ฉด ์ด๋ ‡๋‹ค. ์œตํ•ฉํ•˜๋Š” ๋žญ์ปค๊ฐ€ ๋‘˜์ด๋ฉด ์ •์ฑ…์€ ์–‘์ชฝ์— ๋‹ค ๊ฑธ์–ด์•ผ ํ•œ๋‹ค. ํ•œ์ชฝ์—๋งŒ ๊ฑด ์ •์ฑ…์€ ์œตํ•ฉ ๋น„์ค‘๋งŒํผ ๋ฌฝ์–ด์ง„๋‹ค. ์ตœ์‹  ๊ฐ€์‚ฐ์ ์—์„œ ํ•œ ๋ฒˆ ๊ฒช๊ณ , ๋””๋ ‰ํ† ๋ฆฌ ๊ฐ€์ค‘์น˜์—์„œ ๋˜‘๊ฐ™์ด ๋˜ ๊ฒช์—ˆ๋‹ค.

์ตœ์‹  ๊ฐ€์‚ฐ์ ์€ ๊ณ„๋‹จ์ด ์•„๋‹ˆ๋ผ ์ง€์ˆ˜ ๊ฐ์‡ ๋กœ ๋„ฃ๋Š”๋‹ค. ๋‚ ์งœ ๊ตฌ๊ฐ„์„ ๊ณ„๋‹จ์œผ๋กœ ๋‚˜๋ˆ„๋ฉด ๊ฒฝ๊ณ„ ํ•˜๋ฃจ ์ฐจ์ด๋กœ ์ˆœ์œ„๊ฐ€ ํˆญ ํŠ„๋‹ค. ์˜ค๋Š˜ ๋ฌธ์„œ์— ์ตœ๋Œ€ ๋ณด์ •์„ ์ฃผ๊ณ  ๋ฐ˜๊ฐ๊ธฐ 60์ผ๋กœ ์ค„์˜€๋‹ค.

๋งˆ์ง€๋ง‰ ํ•œ ์นธ: ํ•œ๊ธ€ ์ž…๋ ฅ๊ธฐ์—์„œ Enter๋Š” "์—ด๊ธฐ"๊ฐ€ ์•„๋‹ˆ๋‹ค

์›น UI๋ฅผ ๋ถ™์ด๊ณ  ๊ฒ€์ƒ‰์ฐฝ์— ๋ฐฑ์—…์„œ๋ฒ„๋ฅผ ์ณค๋”๋‹ˆ, ๋‚ฑ๋ง์„ ๋‹ค ์น˜๊ธฐ๋„ ์ „์— ํŒŒ์ผ์ด ์—ด๋ ค ๋ฒ„๋ ธ๋‹ค.

ํ•œ๊ธ€ ์ž…๋ ฅ๊ธฐ๋Š” Enter๋กœ ์กฐํ•ฉ ์ค‘์ธ ๊ธ€์ž๋ฅผ ํ™•์ •ํ•œ๋‹ค. ์„œ๋ฒ„๋ฅผ ์™„์„ฑํ•˜๋ ค๊ณ  ๋ˆ„๋ฅธ Enter๊ฐ€ ๊ทธ๋Œ€๋กœ "๊ฒฐ๊ณผ ์—ด๊ธฐ"๋กœ ์ „๋‹ฌ๋œ ๊ฒƒ์ด๋‹ค. ์˜๋ฌธ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ ˆ๋Œ€ ์žฌํ˜„๋˜์ง€ ์•Š๋Š”๋‹ค.

if (e.isComposing || e.keyCode === 229) return;

isComposing์€ ์ž…๋ ฅ๊ธฐ๊ฐ€ ๊ธ€์ž๋ฅผ ์กฐํ•ฉํ•˜๋Š” ๋™์•ˆ ์ฐธ์ด๋‹ค.mdn-iscomposing ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ € ๋Œ€์‘์œผ๋กœ keyCode === 229๋ฅผ ๊ฐ™์ด ๋ณธ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ฒ€์ƒ‰์ฐฝ์—์„œ Enter์˜ ๊ธฐ๋ณธ ๋œป์€ "์ง€๊ธˆ ์ฐพ๊ธฐ" ๋กœ ๋‘๋Š” ํŽธ์ด ์•ˆ์ „ํ•˜๋‹ค. ์—ฌ๋Š” ๊ฒƒ์€ Ctrl+Enter, ๋˜๋Š” ํ™”์‚ดํ‘œ๋กœ ๊ฒฐ๊ณผ์— ๋‚ด๋ ค๊ฐ„ ๋’ค์˜ Enter๋‹ค. ํ•œ ๋ฒˆ์˜ ์˜ค์กฐ์ž‘์ด ํŒŒ์ผ์„ ์—ฌ๋Š” ๊ฒƒ์ด๋ฉด ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ์‚ญ์ œ๋‚˜ ์ „์†ก์ด๋ฉด ๊ทธ๋ ‡์ง€ ์•Š๋‹ค.

์‹ค์ธก

๊ฐœ์ธ ๋ฉ”๋ชจ 663๊ฐœ ํŒŒ์ผ์„ ์ œ๋ชฉ(heading) ๋‹จ์œ„ 26,777๊ฐœ ์„น์…˜์œผ๋กœ ์ชผ๊ฐœ ์ƒ‰์ธํ•œ ๊ฒฐ๊ณผ๋‹ค.

ํ•ญ๋ชฉ ๊ฐ’
์ƒ‰์ธ 26,777 ์„น์…˜ / DB 88.6MB / ์ „์ฒด 15.3์ดˆ / ์ฆ๋ถ„ 0.22์ดˆ
์ž„๋ฒ ๋”ฉ 26,076๊ฑด(100%), 384์ฐจ์› fp16, CPU
์‘๋‹ต ํ‚ค์›Œ๋“œ๋งŒ 53284ms, ํ‚ค์›Œ๋“œ์™€ ์˜๋ฏธ 283693ms
ํ…Œ์ŠคํŠธ 88๊ฐœ

์˜๋ฏธ ์ธต์ด ์‹ค์ œ๋กœ ๊ฑด์ง€๋Š” ๊ฒƒ๋“ค์ด๋‹ค. ๋””์Šคํฌ ๋Š˜๋ฆฌ๊ธฐ๋กœ ใ€Œ๋””์Šคํฌ ์šฉ๋Ÿ‰ ์ฆ์„คใ€, ์ ‘์†์ด ์•ˆ ๋จ์œผ๋กœ ใ€Œpsql ์ ‘๊ทผ ์•ˆ๋˜๋Š”๋ฐใ€, ๋กœ๊ทธ์ธ ์‹คํŒจ ์›์ธ์œผ๋กœ ใ€ŒAuthentication test failedใ€. ์„ธ ์งˆ์˜ ๋ชจ๋‘ ๋‚ฑ๋ง์ด ํ•˜๋‚˜๋„ ๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค.

์ž„๋ฒ ๋”ฉ ์ ์žฌ์œจ์ด ์ ˆ๋ฐ˜ ๋ฏธ๋งŒ์ด๋ฉด ์˜๋ฏธ ์ธต์„ ์ž๋™์œผ๋กœ ๋„๊ณ  ํ‚ค์›Œ๋“œ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ๋‚ธ๋‹ค. ์ƒ‰์ธ์„ ๋‹ค์‹œ ๋งŒ๋“œ๋Š” ๋™์•ˆ์—๋„ ๊ฒ€์ƒ‰์€ ๊ณ„์†๋ผ์•ผ ํ•œ๋‹ค.

์ •๋ฆฌ

  1. ๊ธฐ๋ณธ ํ† ํฌ๋‚˜์ด์ € ๋‘ ๊ฐœ๊ฐ€ ํ•œ๊ตญ์–ด์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ์ด์œ ๋กœ ์‹คํŒจํ•œ๋‹ค. unicode61์€ ์กฐ์‚ฌ์—, trigram์€ ๋‘ ๊ธ€์ž ์งˆ์˜์— ๋ง‰ํžŒ๋‹ค. ์˜์–ด ํ…Œ์ŠคํŠธ๋กœ๋Š” ๋‘˜ ๋‹ค ์•ˆ ๋ณด์ธ๋‹ค
  2. 2-gram์€ ์ƒ‰์ธ๋งŒ์ด ์•„๋‹ˆ๋ผ ์งˆ์˜์—๋„ ๊ฐ™์ด ์ ์šฉํ•ด์•ผ ํ•˜๊ณ , ํ•œ ๋‚ฑ๋ง์˜ ์กฐ๊ฐ์€ phrase๋กœ ๋ฌถ์–ด์•ผ ํ•œ๋‹ค. AND๋กœ ํ’€๋ฉด ์กฐ๊ฐ์ด ํฉ์–ด์ง„ ๋ฌธ์„œ๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค
  3. ์ธ์ ‘ ์กฐ๊ฑด์„ NEAR๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค. ์ˆœ์„œ๋ฅผ ์•ˆ ๋ณด๋ฏ€๋กœ ์Šคํ† ์–ด๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์— ๊ฑธ๋ฆฐ๋‹ค
  4. 2-gram์€ ๋ถ™์—ฌ ์“ด ์งˆ์˜์—์„œ ๋„์–ด ์“ด ๋ฌธ์„œ๋ฅผ ๋ชป ์žก๋Š”๋‹ค. ๊ฐ€๋ฅธ ํ‘œ๊ธฐ๋ฅผ OR๋กœ ํ•จ๊ป˜ ๋˜์ ธ ํ•œ์ชฝ ๋ฐฉํ–ฅ๋งŒ ๋ณด์™„ํ•œ๋‹ค
  5. ์žฌ์ •๋ ฌ์€ ๊ฒ€์ƒ‰์ด ์•„๋‹ˆ๋‹ค. ํ›„๋ณด๋ฅผ ๋งŒ๋“œ๋Š” ๋‹จ๊ณ„์—์„œ ํ•ฉ์ง‘ํ•ฉ์„ ๋งŒ๋“ค์ง€ ์•Š์œผ๋ฉด ๋‚ฑ๋ง์ด ์•ˆ ๊ฒน์น˜๋Š” ๋ฌธ์„œ๋Š” ์˜์˜ ๋ชป ์ฐพ๋Š”๋‹ค
  6. RRF๋งŒ์œผ๋กœ ์ค„ ์„ธ์šฐ๋ฉด ํ•œ ์ธต์—์„œ 1๋“ฑ์ธ ๋ฌธ์„œ๊ฐ€ ๋‘ ์ธต์—์„œ ์–ด์ค‘๊ฐ„ํ•œ ๋ฌธ์„œ์— ์ง„๋‹ค. ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ ๋ฌถ์Œ์„ ๋จผ์ € ๊ฐ€๋ฅด๊ณ  ๊ทธ ์•ˆ์—์„œ ์œตํ•ฉํ•œ๋‹ค
  7. ๋žญ์ปค๊ฐ€ ๋‘˜์ด๋ฉด ์ •์ฑ…์€ ์–‘์ชฝ์— ๋‹ค ๊ฑธ์–ด์•ผ ํ•œ๋‹ค. ํ•œ์ชฝ์—๋งŒ ๊ฑด ๋ณด์ •์€ ์œตํ•ฉ ๋น„์ค‘๋งŒํผ ๋ฌฝ์–ด์ง„๋‹ค

๊ฐ€์žฅ ์ค‘์š”ํ•œ ํ•˜๋‚˜๋ฅผ ๊ณ ๋ฅด๋ผ๋ฉด ๋‹ค์„ฏ ๋ฒˆ์งธ๋‹ค. ๋‚˜๋จธ์ง€๋Š” ๊ณ ์น˜๋ฉด ๋˜๋Š” ๋ฒ„๊ทธ์ง€๋งŒ, ์žฌ์ •๋ ฌ๋กœ ๋งŒ๋“ค์–ด ๋‘” ๊ตฌ์กฐ๋Š” ๋ชป ์ฐพ๋Š” ๋ฌธ์„œ๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค ์ž์ฒด๊ฐ€ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š”๋‹ค. ๊ฒฐ๊ณผ๋Š” ์–ธ์ œ๋‚˜ ๊ทธ๋Ÿด๋“ฏํ•˜๊ฒŒ ๋‚˜์˜ค๊ณ , ์—†๋Š” ๊ฒƒ์€ ์กฐ์šฉํžˆ ์—†๋‹ค.

  • ํ•œ๊ตญ์–ด ๋ฌธ์„œ๋ฅผ ์ƒ‰์ธํ•˜๊ธฐ ์ „์— ๋‘ ๊ธ€์ž ๋‚ฑ๋ง๋กœ ๋จผ์ € ๊ฒ€์ƒ‰ํ•ด ๋ณธ๋‹ค. 0๊ฑด์ด๋ฉด ํ† ํฌ๋‚˜์ด์ € ๋ฌธ์ œ๋‹ค
  • 2-gram ๋ณ€ํ™˜๊ธฐ๋ฅผ ํ•œ ๊ณณ์— ๋‘๊ณ  ์ƒ‰์ธ๊ณผ ์งˆ์˜๊ฐ€ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅด๊ฒŒ ํ•œ๋‹ค
  • ์˜๋ฌธ๊ณผ ์ˆซ์ž๋Š” 2-gram ๋Œ€์ƒ์—์„œ ๋บ€๋‹ค. ์ƒ‰์ธ๋งŒ ์ปค์ง€๊ณ  ์ •ํ™•๋„๋Š” ๋–จ์–ด์ง„๋‹ค
  • ํ•œ ๋‚ฑ๋ง์—์„œ ๋‚˜์˜จ ์กฐ๊ฐ์€ ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๋ฌถ์–ด ์ธ์ ‘ phrase๋กœ ๋˜์ง„๋‹ค
  • ์ธ์ ‘ ์กฐ๊ฑด์— NEAR๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. ์ˆœ์„œ๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค
  • ๋ถ™์—ฌ ์“ด ์งˆ์˜๋ฅผ ๋‘ ์กฐ๊ฐ์œผ๋กœ ๊ฐ€๋ฅธ ํ‘œ๊ธฐ๋„ OR๋กœ ํ•จ๊ป˜ ๋˜์ง„๋‹ค
  • ๋‚ฑ๋ง ์ „๋ถ€ AND๋ฅผ ์œ ์ผํ•œ ๊ฒฝ๋กœ๋กœ ๋‘์ง€ ์•Š๋Š”๋‹ค. 0๊ฑด์ด๋ฉด OR๋กœ ๋„“ํžˆ๊ณ  ์ ์ค‘ ์ˆ˜๋กœ ์ค„ ์„ธ์šด๋‹ค
  • ์ ์ค‘ ์ˆ˜๋ฅผ ์ •๋ ฌ ํ‚ค๋กœ ๋”ฐ๋กœ ์“ฐ์ง€ ๋ง๊ณ  ์ ์ˆ˜์— ์ ‘์–ด ๋„ฃ๋Š”๋‹ค
  • bm25()๋Š” ๋‚ฎ์„์ˆ˜๋ก ์ข‹์€ ์ ์ˆ˜๋‹ค. ๋ถ€ํ˜ธ๋ฅผ ํ™•์ธํ•œ๋‹ค
  • ์˜๋ฏธ ๊ฒ€์ƒ‰์„ ์žฌ์ •๋ ฌ์ด ์•„๋‹ˆ๋ผ ํ•ฉ์ง‘ํ•ฉ์œผ๋กœ ๋ถ™์ธ๋‹ค
  • RRF ์ „์— "์งˆ์˜ ๋‚ฑ๋ง์ด ์ „๋ถ€ ๊ฑธ๋ฆฐ ๊ฒƒ"์„ ์•ž ๋ฌถ์Œ์œผ๋กœ ๊ฐ€๋ฅธ๋‹ค
  • ๋””๋ ‰ํ† ๋ฆฌ ๊ฐ€์ค‘์น˜์™€ ์ตœ์‹  ๊ฐ€์‚ฐ์ ์„ ์˜๋ฏธ ์ชฝ ์ˆœ์œ„์—๋„ ๋˜‘๊ฐ™์ด ๊ฑด๋‹ค
  • ๋ฒกํ„ฐ ๊ฒฐ๊ณผ์— ์œ ์‚ฌ๋„ ๋ฐ”๋‹ฅ์„ ์„ ๋‘”๋‹ค. ๊ฐ’์€ ์‹ค์ œ ๋ถ„ํฌ๋ฅผ ๋ณด๊ณ  ์ •ํ•œ๋‹ค
  • ํ•œ๊ธ€ ์ž…๋ ฅ๊ธฐ ๋Œ€์‘์œผ๋กœ isComposing์„ ํ™•์ธํ•œ๋‹ค. ๊ฒ€์ƒ‰์ฐฝ์˜ Enter๋Š” "์ง€๊ธˆ ์ฐพ๊ธฐ"๋กœ ๋‘”๋‹ค

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ์„ CPU์—์„œ ์šด์˜ํ•  ๋•Œ์˜ ์„ ํƒ ๊ธฐ์ค€์„ ๋‹ค๋ฃฌ๋‹ค. ํฐ ๋ชจ๋ธ์ด ํ•ญ์ƒ ์ข‹์€ ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ, int8 ์–‘์žํ™”๊ฐ€ CPU์—์„œ ๊ธฐ๋Œ€๋งŒํผ ์•ˆ ๋นจ๋ผ์ง€๋Š” ๊ฒƒ, ๋ฒกํ„ฐ๋ฅผ ๋ฌด์—‡์œผ๋กœ ํ‚ค์ž‰ํ•ด์•ผ ์žฌ์ƒ‰์ธ ๋น„์šฉ์ด ์‚ฌ๋ผ์ง€๋Š”์ง€๋ฅผ ์‹ค์ธก์œผ๋กœ ์ •๋ฆฌํ•œ๋‹ค.

์ฐธ๊ณ ๋ฌธํ—Œ


์ด์–ด์„œ ์ฝ๊ธฐ


  1. SQLite. "SQLite FTS5 Extension - Tokenizers" (unicode61์€ ๊ณต๋ฐฑ๊ณผ ๋ฌธ์žฅ๋ถ€ํ˜ธ ๊ธฐ์ค€, trigram์€ ์—ฐ์†ํ•œ ์„ธ ๊ธ€์ž๋ฅผ ํ† ํฐ์œผ๋กœ ๋ณธ๋‹ค). https://www.sqlite.org/fts5.html#tokenizersโ†ฉ
  2. SQLite. "SQLite FTS5 Extension - FTS5 Phrases" (ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๋ฌถ์€ ๋ฌธ์ž์—ด์€ ์ˆœ์„œ๊ฐ€ ๋ณด์กด๋œ ํ•˜๋‚˜์˜ phrase๊ฐ€ ๋œ๋‹ค). https://www.sqlite.org/fts5.html#fts5_phrasesโ†ฉ
  3. SQLite. "SQLite FTS5 Extension - NEAR Queries" (๊ฑฐ๋ฆฌ N ์•ˆ์— ๋ชจ์—ฌ ์žˆ์œผ๋ฉด ๋งค์น˜, ์ˆœ์„œ๋Š” ์กฐ๊ฑด์ด ์•„๋‹ˆ๋‹ค). https://www.sqlite.org/fts5.html#fts5_near_queriesโ†ฉ
  4. SQLite. "SQLite FTS5 Extension - The bm25() function" (๋” ๋‚˜์€ ๋งค์น˜์ผ์ˆ˜๋ก ์ˆ˜์น˜๊ฐ€ ๋‚ฎ๋‹ค). https://www.sqlite.org/fts5.html#the_bm25_functionโ†ฉ
  5. Wang, L. et al. "Multilingual E5 Text Embeddings: A Technical Report" (2024). https://arxiv.org/abs/2402.05672โ†ฉ
  6. Cormack, G. V., Clarke, C. L. A., Buettcher, S. "Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods" SIGIR 2009. https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf (DOI 10.1145/1571941.1572114)โ†ฉ
  7. MDN Web Docs. "KeyboardEvent.isComposing" (์ž…๋ ฅ๊ธฐ๊ฐ€ ๊ธ€์ž๋ฅผ ์กฐํ•ฉํ•˜๋Š” ๋™์•ˆ true). https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/isComposingโ†ฉ

@leekh8
๋ณด์•ˆ, ์›น ๊ฐœ๋ฐœ, Python์„ ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ