๊ฐœ์š”

OpenSearch์˜ RRF(Reciprocal Rank Fusion)๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰์—์„œ ์—ฌ๋Ÿฌ ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ˆœ์œ„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐํ•ฉํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. OpenSearch 2.19์—์„œ score-ranker-processor์˜ ์ผ๋ถ€๋กœ ๋„์ž…๋˜์—ˆ์œผ๋ฉฐ1, Neural Search ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํ†ตํ•ด ์ œ๊ณต๋œ๋‹ค.

RRF๋Š” ์ ์ˆ˜(score) ๋Œ€์‹  ์ˆœ์œ„(rank)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ, ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒ€์ƒ‰ ๋ฐฉ์‹(ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰, ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ๋“ฑ)์˜ ์ ์ˆ˜ ์Šค์ผ€์ผ ์ฐจ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค.

๋ฐฐ๊ฒฝ

๊ธฐ์กด ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰์˜ ํ•œ๊ณ„

OpenSearch 2.10๋ถ€ํ„ฐ ์ง€์›๋œ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰์€ normalization-processor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ ์ˆ˜๋ฅผ ์ •๊ทœํ™”ํ•˜๊ณ  ๊ฒฐํ•ฉํ–ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค:

  • ์ ์ˆ˜ ์ •๊ทœํ™”์˜ ๋ณต์žก์„ฑ: Min-max, L2 ๋“ฑ ์ •๊ทœํ™” ๊ธฐ๋ฒ• ์„ ํƒ ํ•„์š”
  • ์ด์ƒ์น˜ ๋ฏผ๊ฐ์„ฑ: ๊ทน๋‹จ์ ์ธ ์ ์ˆ˜ ๊ฐ’์ด ๊ฒฐ๊ณผ๋ฅผ ์™œ๊ณก
  • ๊ฐ€์ค‘์น˜ ์กฐ์ • ํ•„์š”: ๊ฐ ๊ฒ€์ƒ‰ ๋ฐฉ์‹์˜ ๊ฐ€์ค‘์น˜๋ฅผ ์ˆ˜๋™์œผ๋กœ ํŠœ๋‹

RRF์˜ ํ•ด๊ฒฐ์ฑ…

RRF๋Š” ์ ์ˆ˜๊ฐ€ ์•„๋‹Œ ์ˆœ์œ„๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ:

  • ์ ์ˆ˜ ์ •๊ทœํ™” ๋ถˆํ•„์š”
  • ์ด์ƒ์น˜์— ๊ฐ•๊ฑด
  • ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์ตœ์†Œํ™” (๊ธฐ๋ณธ๊ฐ’ k=60)

์ž‘๋™ ์›๋ฆฌ

RRF ๊ณต์‹

๊ฐ ๋ฌธ์„œ์˜ ์ตœ์ข… ์ ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ณ„์‚ฐ๋œ๋‹ค:

์—ฌ๊ธฐ์„œ:

  • = ๋ฌธ์„œ
  • = ์ฟผ๋ฆฌ ์ง‘ํ•ฉ (hybrid query์˜ ์„œ๋ธŒ์ฟผ๋ฆฌ๋“ค)
  • = ์ˆœ์œ„ ์ƒ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 60)
  • = ์ฟผ๋ฆฌ ์—์„œ ๋ฌธ์„œ ์˜ ์ˆœ์œ„ (1๋ถ€ํ„ฐ ์‹œ์ž‘)

์˜ˆ์‹œ

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ฟผ๋ฆฌ ๊ตฌ์„ฑ:

  • ์ฟผ๋ฆฌ 1 (BM25): ๋ฌธ์„œ A(1์œ„), ๋ฌธ์„œ B(2์œ„), ๋ฌธ์„œ C(3์œ„)
  • ์ฟผ๋ฆฌ 2 (Neural): ๋ฌธ์„œ C(1์œ„), ๋ฌธ์„œ A(2์œ„), ๋ฌธ์„œ D(3์œ„)

RRF ์ ์ˆ˜ ๊ณ„์‚ฐ (k=60):

  • ๋ฌธ์„œ A:
  • ๋ฌธ์„œ B:
  • ๋ฌธ์„œ C:
  • ๋ฌธ์„œ D:

์ตœ์ข… ์ˆœ์œ„: A โ†’ C โ†’ B โ†’ D

๊ตฌ์„ฑ ๋ฐฉ๋ฒ•

1. Search Pipeline ์ƒ์„ฑ

RRF๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € score-ranker-processor๋ฅผ ํฌํ•จํ•˜๋Š” search pipeline์„ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค.

๊ธฐ๋ณธ ์„ค์ • (k=60)

PUT /_search/pipeline/rrf-pipeline
{
  "description": "RRF ๊ธฐ๋ฐ˜ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ฒ€์ƒ‰ ํŒŒ์ดํ”„๋ผ์ธ",
  "phase_results_processors": [
    {
      "score-ranker-processor": {
        "combination": {
          "technique": "rrf"
        }
      }
    }
  ]
}

์ปค์Šคํ…€ rank constant

PUT /_search/pipeline/rrf-pipeline
{
  "description": "์ปค์Šคํ…€ rank constant๋ฅผ ์‚ฌ์šฉํ•˜๋Š” RRF",
  "phase_results_processors": [
    {
      "score-ranker-processor": {
        "combination": {
          "technique": "rrf",
          "rank_constant": 40
        }
      }
    }
  ]
}

rank_constant์˜ ์—ญํ• :

  • ๊ฐ’์ด ์ž‘์„์ˆ˜๋ก: ์ƒ์œ„ ๋ฌธ์„œ์˜ ์˜ํ–ฅ๋ ฅ ์ฆ๊ฐ€ (์ˆœ์œ„ ์ฐจ์ด ๊ฐ•์กฐ)
  • ๊ฐ’์ด ํด์ˆ˜๋ก: ์ˆœ์œ„ ๊ฐ„ ์ ์ˆ˜ ์ฐจ์ด ํ‰ํƒ„ํ™” (๊ท ๋“ฑํ•œ ์˜ํ–ฅ)
  • ์ œ์•ฝ: 1 ์ด์ƒ์ด์–ด์•ผ ํ•จ

๊ฐ€์ค‘์น˜ ์ ์šฉ

์„œ๋ธŒ์ฟผ๋ฆฌ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค (๊ธฐ๋ณธ๊ฐ’์€ ๋ชจ๋‘ 1.0):

PUT /_search/pipeline/weighted-rrf-pipeline
{
  "description": "๊ฐ€์ค‘์น˜๊ฐ€ ์ ์šฉ๋œ RRF",
  "phase_results_processors": [
    {
      "score-ranker-processor": {
        "combination": {
          "technique": "rrf",
          "rank_constant": 60,
          "parameters": {
            "weights": [0.7, 0.3]
          }
        }
      }
    }
  ]
}

์œ„ ์˜ˆ์‹œ์—์„œ:

  • ์ฒซ ๋ฒˆ์งธ ์ฟผ๋ฆฌ (์˜ˆ: BM25): ๊ฐ€์ค‘์น˜ 0.7
  • ๋‘ ๋ฒˆ์งธ ์ฟผ๋ฆฌ (์˜ˆ: Neural): ๊ฐ€์ค‘์น˜ 0.3

2. ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ฟผ๋ฆฌ ์‹คํ–‰

์ƒ์„ฑํ•œ pipeline์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

GET /my-index/_search
{
  "query": {
    "hybrid": {
      "queries": [
        {
          "match": {
            "content": "OpenSearch vector search"
          }
        },
        {
          "neural": {
            "content_embedding": {
              "query_text": "OpenSearch vector search",
              "model_id": "your-model-id",
              "k": 10
            }
          }
        }
      ]
    }
  },
  "search_pipeline": "rrf-pipeline"
}

์—ฌ๋Ÿฌ ๊ฒ€์ƒ‰ ๋ฐฉ์‹ ๊ฒฐํ•ฉ

GET /my-index/_search
{
  "query": {
    "hybrid": {
      "queries": [
        {
          "match": {
            "title": "machine learning"
          }
        },
        {
          "neural": {
            "embedding": {
              "query_text": "machine learning",
              "model_id": "model-id"
            }
          }
        },
        {
          "neural_sparse": {
            "sparse_embedding": {
              "query_text": "machine learning",
              "model_id": "sparse-model-id"
            }
          }
        }
      ]
    }
  },
  "search_pipeline": "weighted-rrf-pipeline"
}

์‚ฌ์šฉ ์‚ฌ๋ก€

1. ํ‚ค์›Œ๋“œ + ์˜๋ฏธ ๊ฒ€์ƒ‰

์‹œ๋‚˜๋ฆฌ์˜ค: ์ •ํ™•ํ•œ ํ‚ค์›Œ๋“œ ๋งค์นญ๊ณผ ์˜๋ฏธ์  ์œ ์‚ฌ์„ฑ์„ ๋ชจ๋‘ ๊ณ ๋ ค

{
  "query": {
    "hybrid": {
      "queries": [
        {
          "multi_match": {
            "query": "OpenSearch",
            "fields": ["title^2", "content"]
          }
        },
        {
          "neural": {
            "content_embedding": {
              "query_text": "OpenSearch",
              "model_id": "dense-model"
            }
          }
        }
      ]
    }
  },
  "search_pipeline": "rrf-pipeline"
}

ํšจ๊ณผ:

  • BM25๊ฐ€ โ€œOpenSearchโ€ ํ‚ค์›Œ๋“œ๋ฅผ ์ •ํ™•ํžˆ ํฌํ•จํ•œ ๋ฌธ์„œ ์šฐ์„ 
  • Neural search๊ฐ€ โ€œElasticsearchโ€, โ€œvector databaseโ€ ๋“ฑ ๊ด€๋ จ ๊ฐœ๋… ํฌํ•จ

2. Sparse + Dense ๋ฒกํ„ฐ ๊ฒ€์ƒ‰

์‹œ๋‚˜๋ฆฌ์˜ค: ํฌ์†Œ ๋ฒกํ„ฐ์™€ ๋ฐ€์ง‘ ๋ฒกํ„ฐ์˜ ์žฅ์  ๊ฒฐํ•ฉ

{
  "query": {
    "hybrid": {
      "queries": [
        {
          "neural_sparse": {
            "sparse_embedding": {
              "query_text": "vector similarity search",
              "model_id": "opensearch-neural-sparse-encoding-v2"
            }
          }
        },
        {
          "neural": {
            "dense_embedding": {
              "query_text": "vector similarity search",
              "model_id": "sentence-transformers"
            }
          }
        }
      ]
    }
  },
  "search_pipeline": "rrf-pipeline"
}

ํšจ๊ณผ:

  • Sparse: ๋ช…์‹œ์  ๋‹จ์–ด ๋งค์นญ + term expansion
  • Dense: ๊นŠ์€ ์˜๋ฏธ์  ์œ ์‚ฌ์„ฑ

3. ๋‹ค์ค‘ ํ•„๋“œ ๊ฒ€์ƒ‰

์‹œ๋‚˜๋ฆฌ์˜ค: ์ œ๋ชฉ๊ณผ ๋ณธ๋ฌธ์— ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒ€์ƒ‰ ์ „๋žต ์ ์šฉ

{
  "query": {
    "hybrid": {
      "queries": [
        {
          "match": {
            "title": {
              "query": "OpenSearch tutorial",
              "boost": 2.0
            }
          }
        },
        {
          "match": {
            "content": "OpenSearch tutorial"
          }
        },
        {
          "neural": {
            "content_embedding": {
              "query_text": "OpenSearch tutorial",
              "model_id": "model-id"
            }
          }
        }
      ]
    }
  },
  "search_pipeline": "weighted-rrf-pipeline"
}

RRF vs Normalization

OpenSearch 2.10~2.18์—์„œ ์‚ฌ์šฉ๋œ normalization ๋ฐฉ์‹๊ณผ ๋น„๊ต:

์ธก๋ฉดNormalizationRRF
๊ธฐ๋ฐ˜์ ์ˆ˜(score)์ˆœ์œ„(rank)
์ •๊ทœํ™”ํ•„์š” (min-max, L2 ๋“ฑ)๋ถˆํ•„์š”
์ด์ƒ์น˜ ์˜ํ–ฅ๋†’์Œ๋‚ฎ์Œ
ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹๋ณต์žก (๊ธฐ๋ฒ•, ๊ฐ€์ค‘์น˜)๋‹จ์ˆœ (k ๊ฐ’)
ํˆฌ๋ช…์„ฑ๋‚ฎ์Œ (์ ์ˆ˜ ๋ณ€ํ™˜)๋†’์Œ (์ˆœ์œ„ ๊ธฐ๋ฐ˜)
๋„์ž… ๋ฒ„์ „2.102.19
3.0 ์ถ”๊ฐ€ ๊ธฐ๋ŠฅZ-score normalization ์ถ”๊ฐ€๊ณ„์† ์ง€์›

์–ธ์ œ RRF๋ฅผ ์‚ฌ์šฉํ• ๊นŒ?

RRF ๊ถŒ์žฅ ์ƒํ™ฉ:

  • ์„œ๋กœ ๋‹ค๋ฅธ ์ ์ˆ˜ ์Šค์ผ€์ผ์˜ ๊ฒ€์ƒ‰ ๋ฐฉ์‹ ๊ฒฐํ•ฉ
  • ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์‹œ๊ฐ„์ด ์ œํ•œ์ ์ธ ๊ฒฝ์šฐ
  • ๊ฐ•๊ฑดํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
  • ์ˆœ์œ„๊ฐ€ ์ ์ˆ˜๋ณด๋‹ค ์‹ ๋ขฐํ•  ๋งŒํ•œ ๊ฒฝ์šฐ

Normalization ๊ณ ๋ ค ์ƒํ™ฉ:

  • ์ ์ˆ˜ ์ž์ฒด์˜ ์˜๋ฏธ๊ฐ€ ์ค‘์š”ํ•œ ๊ฒฝ์šฐ
  • ๋งค์šฐ ์„ธ๋ฐ€ํ•œ ๊ฐ€์ค‘์น˜ ์กฐ์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • ํŠน์ • ์ •๊ทœํ™” ๊ธฐ๋ฒ•์— ๋Œ€ํ•œ ๋„๋ฉ”์ธ ์ง€์‹์ด ์žˆ๋Š” ๊ฒฝ์šฐ

๋ชจ๋ฒ” ์‚ฌ๋ก€

1. rank_constant ์„ ํƒ

{
  "combination": {
    "technique": "rrf",
    "rank_constant": 60  // ๊ธฐ๋ณธ๊ฐ’, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์ ํ•ฉ
  }
}
  • k=60: ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ถŒ์žฅ (์› ๋…ผ๋ฌธ ๊ธฐ์ค€)
  • k=20~40: ์ƒ์œ„ ๋ฌธ์„œ ์˜ํ–ฅ๋ ฅ ์ฆ๊ฐ€, ๋ช…ํ™•ํ•œ ๊ด€๋ จ์„ฑ ์ฐจ์ด ์žˆ์„ ๋•Œ
  • k=80~100: ์ˆœ์œ„ ๊ฐ„ ์ฐจ์ด ์™„ํ™”, ํƒ์ƒ‰์  ๊ฒ€์ƒ‰

2. ๊ฐ€์ค‘์น˜ ์„ค์ •

{
  "combination": {
    "technique": "rrf",
    "parameters": {
      "weights": [0.6, 0.4]  // BM25: 60%, Neural: 40%
    }
  }
}

๊ฐ€์ค‘์น˜ ๊ฒฐ์ • ๊ธฐ์ค€:

  • ํ‚ค์›Œ๋“œ ์ค‘์‹ฌ ๋„๋ฉ”์ธ (๋ฒ•๋ฅ , ๊ธฐ์ˆ  ๋ฌธ์„œ): BM25 ๊ฐ€์ค‘์น˜ ๋†’์ž„ (0.7~0.8)
  • ์˜๋ฏธ ์ค‘์‹ฌ ๋„๋ฉ”์ธ (๋‰ด์Šค, ๋ธ”๋กœ๊ทธ): Neural ๊ฐ€์ค‘์น˜ ๋†’์ž„ (0.6~0.7)
  • ๊ท ํ˜• ์ ‘๊ทผ: ๋™์ผ ๊ฐ€์ค‘์น˜ (0.5, 0.5) ๋˜๋Š” ์ƒ๋žต (๊ธฐ๋ณธ๊ฐ’ 1.0)

3. ์ฟผ๋ฆฌ ์ˆœ์„œ

{
  "hybrid": {
    "queries": [
      { "match": { ... } },      // 1๋ฒˆ: BM25 (๊ฐ€์ค‘์น˜ weights[0])
      { "neural": { ... } }       // 2๋ฒˆ: Neural (๊ฐ€์ค‘์น˜ weights[1])
    ]
  }
}

์ค‘์š”: weights ๋ฐฐ์—ด์˜ ์ˆœ์„œ๋Š” queries ๋ฐฐ์—ด ์ˆœ์„œ์™€ ์ผ์น˜ํ•ด์•ผ ํ•จ.

4. ์„ฑ๋Šฅ ์ตœ์ ํ™”

์ธ๋ฑ์Šค ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ์ „๋žต:

  • ์†Œํ˜• ์ธ๋ฑ์Šค (< 100๋งŒ ๋ฌธ์„œ): ๋ชจ๋“  ์ฟผ๋ฆฌ ๋™๋“ฑํ•˜๊ฒŒ ์‹คํ–‰
  • ๋Œ€ํ˜• ์ธ๋ฑ์Šค (> 1000๋งŒ ๋ฌธ์„œ):
    • BM25๋กœ 1์ฐจ ํ•„ํ„ฐ๋ง (์ƒ์œ„ 1000๊ฐœ)
    • Neural๋กœ 2์ฐจ ์ •๋ฐ€ ๊ฒ€์ƒ‰
    • RRF๋กœ ์ตœ์ข… ์ˆœ์œ„ ๊ฒฐ์ •

์ œํ•œ์‚ฌํ•ญ

1. ๋ฒ„์ „ ์š”๊ตฌ์‚ฌํ•ญ

  • OpenSearch 2.19 ์ด์ƒ ํ•„์š”
  • Neural Search ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ํ•„์ˆ˜

2. ์ฟผ๋ฆฌ ์ œ์•ฝ

  • hybrid ์ฟผ๋ฆฌ ํƒ€์ž… ์‚ฌ์šฉ ํ•„์ˆ˜
  • ๊ฐ ์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•จ
  • ์ผ๋ถ€ ์ฟผ๋ฆฌ ํƒ€์ž…๊ณผ ํ˜ธํ™˜ ๋ถˆ๊ฐ€ (bool ์ฟผ๋ฆฌ ๋‚ด๋ถ€ ๋“ฑ)

3. ๊ฐ€์ค‘์น˜ ์ œ์•ฝ

  • ๊ฐ€์ค‘์น˜ ๋ฐฐ์—ด ๊ธธ์ด๋Š” ์ฟผ๋ฆฌ ๊ฐœ์ˆ˜์™€ ๋™์ผํ•ด์•ผ ํ•จ
  • ๊ฐ€์ค‘์น˜๋Š” ์–‘์ˆ˜์—ฌ์•ผ ํ•จ (์Œ์ˆ˜ ๋ถˆ๊ฐ€)
  • ๊ฐ€์ค‘์น˜ ํ•ฉ์ด 1์ผ ํ•„์š”๋Š” ์—†์Œ (์ž๋™ ์ •๊ทœํ™”)

๊ด€๋ จ ๊ธฐ์ˆ 

์ฐธ๊ณ  ์ž๋ฃŒ

๊ณต์‹ ๋ฌธ์„œ

๋ธ”๋กœ๊ทธ ๋ฐ ํŠœํ† ๋ฆฌ์–ผ

GitHub

Footnotes

  1. RFC: Design for Incorporating Reciprocal Rank Fusion into Neural Search โ†ฉ