Multi-Search API
Multi-Search API
1.0에서 도입
이름 그대로 multi-search 작업은 여러 검색 요청을 하나의 요청으로 묶어주는 기능이에요. 그러면 OpenSearch가 검색을 병렬로 실행하므로, 검색마다 요청을 하나씩 보내는 것보다 응답을 더 빨리 받을 수 있어요. OpenSearch는 각 검색을 독립적으로 실행하므로, 하나가 실패해도 다른 것에는 영향을 주지 않아요.
출처: 문서
본문
엔드포인트 (Endpoints)
GET /_msearch
POST /_msearch
GET /{index}/_msearch
POST /{index}/_msearch
경로에 인덱스를 지정하면, 메타데이터 줄에 index 필드가 없는 검색들의 기본 대상이 그 인덱스가 돼요. 경로 매개변수를 생략하고 검색의 메타데이터 줄에도 index를 지정하지 않으면, 그 검색은 모든 인덱스를 대상으로 실행돼요.
쿼리 매개변수 및 메타데이터 옵션 (Query parameters and metadata options)
모든 매개변수는 선택 사항이에요. 일부는 각 메타데이터 줄의 일부로 검색별로 적용할 수도 있어요.
| 매개변수 | 타입 | 설명 | 메타데이터 줄 지원 |
|---|---|---|---|
allow_no_indices |
Boolean | 어떤 인덱스와도 일치하지 않는 와일드카드를 무시할지 여부예요. 기본값은 true예요. |
예 |
cancel_after_time_interval |
Time | 이 시간이 지나면 검색 요청이 취소돼요. 상위·하위 요청 수준 모두 지원돼요. 우선순위는 다음과 같아요: | 예 |
| 1. 하위 수준 매개변수 | |||
| 2. 상위 수준 매개변수 | |||
| 3. 클러스터 설정 | |||
| 기본값은 -1이에요. | |||
ccs_minimize_roundtrips |
Boolean | OpenSearch가 조정 노드와 원격 클러스터 사이의 네트워크 왕복을 최소화하려고 하는지 여부예요(교차 클러스터 검색 요청에만 적용). 기본값은 true예요. |
아니요 |
expand_wildcards |
Enum | 와일드카드 표현식을 실제 인덱스로 확장해요. 여러 값을 쉼표로 조합해요. 지원 값은 all, open, closed, hidden, none이에요. 기본값은 open이에요. |
예 |
ignore_unavailable |
Boolean | 인덱스 목록의 인덱스나 샤드가 없을 때 쿼리를 실패시키지 않고 무시할지 여부예요. 기본값은 false예요. |
예 |
include_named_queries_score |
Boolean | 명명된 쿼리의 점수를 반환할지 여부예요. 기본값은 false예요. |
아니요 |
max_concurrent_searches |
Integer | 최대 동시 검색 수예요. 기본값은 노드 수와 검색 스레드 풀 크기에 따라 달라져요. 값이 클수록 성능이 좋아질 수 있지만 클러스터가 과부하될 위험이 있어요. | 아니요 |
max_concurrent_shard_requests |
Integer | 각 검색이 노드당 실행하는 최대 동시 샤드 요청 수예요. 기본값은 5예요. 값이 클수록 성능이 좋아질 수 있지만 클러스터가 과부하될 위험이 있어요. | 아니요 |
pre_filter_shard_size |
Integer | 쿼리와 일치할 수 없는 샤드를 제거하기 위한 사전 필터(pre-filter) 왕복을 트리거하는 임계값이에요(예: 날짜 범위 필터가 샤드 범위 밖인 경우). 지정하지 않으면 요청이 128개 이상의 샤드를 대상으로 하거나, 읽기 전용 인덱스를 대상으로 하거나, 색인된 필드로 정렬할 때 사전 필터 단계가 실행돼요. 기본값은 128이에요. |
아니요 |
rest_total_hits_as_int |
String | hits.total 속성을 정수(true)로 반환할지 객체(false)로 반환할지 여부예요. 기본값은 false예요. |
아니요 |
routing |
String | 요청의 모든 검색을 특정 샤드로 라우팅하는 데 사용하는 쉼표로 구분된 사용자 지정 라우팅 값이에요. 개별 검색의 라우팅을 설정하려면 메타데이터 줄의 routing 옵션을 사용해요. |
아니요 |
search_type |
String | 관련성 점수에 영향을 줘요. 유효한 옵션은 query_then_fetch와 dfs_query_then_fetch예요. query_then_fetch는 샤드의 term·document 빈도로 문서에 점수를 매기고(더 빠르고 덜 정확), dfs_query_then_fetch는 모든 샤드의 term·document 빈도를 사용해요(더 느리고 더 정확). 기본값은 query_then_fetch예요. |
예 |
typed_keys |
Boolean | 응답에서 집계 이름 앞에 내부 타입을 붙일지 여부예요. 기본값은 false예요. |
아니요 |
메타데이터 전용 옵션 (Metadata-only options)
일부 옵션은 전체 요청에 매개변수로 적용할 수 없어요. 대신 각 메타데이터 줄의 일부로 검색별로 적용할 수 있어요. 모두 선택 사항이에요.
| 옵션 | 타입 | 설명 |
|---|---|---|
index |
String, 문자열 배열 | URL의 일부로 인덱스(또는 여러 인덱스)를 지정하지 않았다면(또는 개별 검색의 URL 값을 덮어쓰려면) 여기에 포함할 수 있어요. 예로 "logs-*"와 ["my-store", "sample_data_ecommerce"]가 있어요. |
preference |
String | 검색을 수행할 노드나 샤드예요. 테스트에 유용할 수 있지만 대부분의 상황에서는 기본 동작이 최상의 검색 지연 시간을 제공해요. 옵션에는 _local, _only_local, _prefer_nodes, _only_nodes, _shards가 있어요. 마지막 세 옵션은 노드나 샤드 목록을 받아요. 예로 "_only_nodes:data-node1,data-node2"와 "_shards:0,1이 있어요. |
request_cache |
Boolean | 결과를 캐시할지 여부예요. 반복 검색의 지연 시간을 줄일 수 있어요. 기본값은 인덱스의 index.requests.cache.enable 설정을 사용해요(새 인덱스의 기본값은 true). |
routing |
String | 쉼표로 구분된 사용자 지정 라우팅 값이에요. 예: "routing": "value1,value2,value3". 요청의 모든 검색에 적용되는 쿼리 매개변수 수준의 routing과 달리, 이 옵션은 개별 검색의 라우팅만 대상으로 해요. |
요청 본문 (Request body)
multi-search 요청 본문은 줄바꿈으로 구분된 JSON(NDJSON) 형식을 사용하며, 메타데이터 줄과 쿼리 줄이 번갈아 나와요:
Metadata\n
Query\n
Metadata\n
Query\n
메타데이터 줄에는 검색 대상 인덱스, 검색 유형 같은 옵션이 포함돼요. 검색별 덮어쓰기가 필요 없으면 메타데이터 줄은 비워({}) 둘 수 있어요.
쿼리 줄은 query DSL을 사용해요.
bulk 작업처럼 JSON을 최소화할 필요는 없어요. 공백은 괜찮지만, 한 줄에 있어야 해요. OpenSearch는 줄바꿈 문자로 multi-search 요청을 파싱하며 요청 본문이 줄바꿈 문자로 끝나야 해요.
이 엔드포인트로 요청을 보낼 때는 Content-Type 헤더를 application/x-ndjson으로 설정해요.
쿼리 본문 필드 (Query body fields)
각 쿼리 줄은 Search API 요청 본문과 같은 매개변수를 받아요. 다음 표는 가장 흔히 쓰는 필드예요.
| 필드 | 데이터 타입 | 설명 |
|---|---|---|
query |
Object | 실행할 query DSL 표현식이에요. Query DSL을 참고해요. |
aggregations |
Object | 검색과 함께 실행할 집계예요. Aggregations을 참고해요. |
from |
Integer | 반환할 hits의 시작 오프셋이에요. 기본값은 0이에요. |
size |
Integer | 반환할 hits 수예요. 기본값은 10이에요. |
sort |
Array 또는 Object | 결과를 정렬할 필드와 순서예요. |
_source |
Boolean, String 또는 Object | 각 hit의 _source에 포함할 필드를 제어해요. |
highlight |
Object | 일치 필드의 하이라이트 설정이에요. |
예시: 여러 인덱스 검색하기 (Searching multiple indexes)
다음 예시는 각 메타데이터 줄에서 대상 인덱스를 지정하면서 여러 인덱스에 쿼리를 실행해요:
GET /_msearch
{ "index": "opensearch_dashboards_sample_data_logs"}
{ "query": { "match_all": {} }, "from": 0, "size": 10}
{ "index": "opensearch_dashboards_sample_data_ecommerce", "search_type": "dfs_query_then_fetch"}
{ "query": { "match_all": {} } }
Python 클라이언트로는 이렇게 호출해요:
response = client.msearch(
body = '''
{ "index": "opensearch_dashboards_sample_data_logs"}
{ "query": { "match_all": {} }, "from": 0, "size": 10}
{ "index": "opensearch_dashboards_sample_data_ecommerce", "search_type": "dfs_query_then_fetch"}
{ "query": { "match_all": {} } }
'''
)
예시: 기본 인덱스 사용하기 (Using a default index)
URL 경로에 인덱스를 지정하면, 메타데이터 줄에 index 필드가 없는 검색들의 기본 대상이 그 인덱스가 돼요. 다음 예시는 각 메타데이터 줄에서 인덱스 이름을 반복하지 않고 products 인덱스에 두 쿼리를 실행해요:
GET /products/_msearch
{}
{"query": {"match": {"product_name": "headphones"}}, "size": 1}
{}
{"query": {"range": {"price": {"gte": 30, "lte": 50}}}}
Python 클라이언트로는 이렇게 호출해요:
response = client.msearch(
index = "products",
body = '''
{}
{"query": {"match": {"product_name": "headphones"}}, "size": 1}
{}
{"query": {"range": {"price": {"gte": 30, "lte": 50}}}}
'''
)
검색 템플릿 사용하기 (Using search templates)
multi-search API는 _msearch/template 엔드포인트를 통해 검색 템플릿을 지원해요. 이렇게 하면 매개변수화된 검색을 실행할 수 있고, 쿼리 구조를 검색 시점에 전달되는 값과 분리해요.
예시: 인라인 템플릿 (Inline templates)
다음 요청은 인라인 템플릿으로 단일 호출에서 두 개의 매개변수화된 검색을 실행해요:
GET _msearch/template
{"index": "products"}
{"source": {"query": {"match": {"product_name": ""}}}, "params": {"search_term": "wireless"}}
{"index": "products"}
{"source": {"query": {"range": {"price": {"lte": ""}}}}, "params": {"max_price": "75"}}
예시: 저장된 템플릿 (Stored templates)
미리 등록된 템플릿을 ID로 참조할 수도 있어요. 먼저 저장된 템플릿을 만들어요:
POST _scripts/product_search_template
{
"script": {
"lang": "mustache",
"source": {
"query": {
"multi_match": {
"query": "",
"fields": ["product_name", "description"]
}
},
"size": ""
}
}
}
POST _scripts/price_range_template
{
"script": {
"lang": "mustache",
"source": {
"query": {
"range": {
"price": {
"gte": "",
"lte": ""
}
}
},
"size": ""
}
}
}
그런 다음 multi-search 요청에서 저장된 템플릿을 사용해요:
GET _msearch/template
{"index": "products"}
{"id": "product_search_template", "params": {"query_text": "bluetooth speaker", "result_count": "5"}}
{"index": "products"}
{"id": "price_range_template", "params": {"min_price": "20", "max_price": "100", "result_count": "3"}}
예시 응답 (Example response)
OpenSearch는 multi-search 요청과 같은 순서로 각 검색의 결과가 담긴 배열을 반환해요.
{
"took" : 2150,
"responses" : [
{
"took" : 2149,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10000,
"relation" : "gte"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "opensearch_dashboards_sample_data_logs",
"_id" : "_fnhBXsBgv2Zxgu9dZ8Y",
"_score" : 1.0,
"_source" : {
"agent" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)",
"bytes" : 4657,
"clientip" : "213.116.129.196",
"extension" : "zip",
"geo" : {
"srcdest" : "CN:US",
"src" : "CN",
"dest" : "US",
"coordinates" : {
"lat" : 42.35083333,
"lon" : -86.25613889
}
},
"host" : "artifacts.opensearch.org",
"index" : "opensearch_dashboards_sample_data_logs",
"ip" : "213.116.129.196",
"machine" : {
"ram" : 16106127360,
"os" : "ios"
},
"memory" : null,
"message" : "213.116.129.196 - - [2018-07-30T14:12:11.387Z] \"GET /opensearch_dashboards/opensearch_dashboards-1.0.0-windows-x86_64.zip HTTP/1.1\" 200 4657 \"-\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\"",
"phpmemory" : null,
"referer" : "http://twitter.com/success/ellison-onizuka",
"request" : "/opensearch_dashboards/opensearch_dashboards-1.0.0-windows-x86_64.zip",
"response" : 200,
"tags" : [
"success",
"info"
],
"timestamp" : "2021-08-02T14:12:11.387Z",
"url" : "https://artifacts.opensearch.org/downloads/opensearch_dashboards/opensearch_dashboards-1.0.0-windows-x86_64.zip",
"utc_time" : "2021-08-02T14:12:11.387Z",
"event" : {
"dataset" : "sample_web_logs"
}
}
},
...
]
},
"status" : 200
},
{
"took" : 1473,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4675,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "opensearch_dashboards_sample_data_ecommerce",
"_id" : "efnhBXsBgv2Zxgu9ap7e",
"_score" : 1.0,
"_source" : {
"category" : [
"Women's Clothing"
],
"currency" : "EUR",
"customer_first_name" : "Gwen",
"customer_full_name" : "Gwen Dennis",
"customer_gender" : "FEMALE",
"customer_id" : 26,
"customer_last_name" : "Dennis",
"customer_phone" : "",
"day_of_week" : "Tuesday",
"day_of_week_i" : 1,
"email" : "[email protected]",
"manufacturer" : [
"Tigress Enterprises",
"Gnomehouse mom"
],
"order_date" : "2021-08-10T16:24:58+00:00",
"order_id" : 576942,
"products" : [
{
"base_price" : 32.99,
"discount_percentage" : 0,
"quantity" : 1,
"manufacturer" : "Tigress Enterprises",
"tax_amount" : 0,
"product_id" : 22182,
"category" : "Women's Clothing",
"sku" : "ZO0036600366",
"taxless_price" : 32.99,
"unit_discount_amount" : 0,
"min_price" : 14.85,
"_id" : "sold_product_576942_22182",
"discount_amount" : 0,
"created_on" : "2016-12-20T16:24:58+00:00",
"product_name" : "Jersey dress - black/red",
"price" : 32.99,
"taxful_price" : 32.99,
"base_unit_price" : 32.99
},
{
"base_price" : 28.99,
"discount_percentage" : 0,
"quantity" : 1,
"manufacturer" : "Gnomehouse mom",
"tax_amount" : 0,
"product_id" : 14230,
"category" : "Women's Clothing",
"sku" : "ZO0234902349",
"taxless_price" : 28.99,
"unit_discount_amount" : 0,
"min_price" : 13.05,
"_id" : "sold_product_576942_14230",
"discount_amount" : 0,
"created_on" : "2016-12-20T16:24:58+00:00",
"product_name" : "Blouse - june bug",
"price" : 28.99,
"taxful_price" : 28.99,
"base_unit_price" : 28.99
}
],
"sku" : [
"ZO0036600366",
"ZO0234902349"
],
"taxful_total_price" : 61.98,
"taxless_total_price" : 61.98,
"total_quantity" : 2,
"total_unique_products" : 2,
"type" : "order",
"user" : "gwen",
"geoip" : {
"country_iso_code" : "US",
"location" : {
"lon" : -118.2,
"lat" : 34.1
},
"region_name" : "California",
"continent_name" : "North America",
"city_name" : "Los Angeles"
},
"event" : {
"dataset" : "sample_ecommerce"
}
}
},
...
]
},
"status" : 200
}
]
}
응답 본문 필드 (Response body fields)
다음 표는 최상위 응답 본문 필드를 정리한 거예요.
| 필드 | 데이터 타입 | 설명 |
|---|---|---|
took |
Integer | OpenSearch가 요청의 모든 검색을 처리하는 데 걸린 총 시간(밀리초)이에요. |
responses |
Array | 검색 응답 객체 배열이에요. 요청의 해당 검색과 같은 순서로 반환돼요. 특정 검색이 완전히 실패하면 그 검색의 배열 항목에는 일반 검색 응답 대신 error 객체와 status 코드가 들어 있어요. |
다음 표는 responses 배열의 각 항목에 있는 필드를 정리한 거예요.
| 필드 | 데이터 타입 | 설명 |
|---|---|---|
took |
Integer | OpenSearch가 개별 검색을 처리하는 데 걸린 시간(밀리초)이에요. |
timed_out |
Boolean | 검색이 완료되기 전에 타임아웃됐는지 여부예요. |
_shards |
Object | 검색에 관련된 샤드 수에 대한 정보예요. total, successful, skipped, failed 개수를 포함해요. |
hits |
Object | 검색 결과예요. 총 hit 수, max_score, 일치하는 hits 배열을 포함해요. |
status |
Integer | 개별 검색 결과의 HTTP 상태 코드예요. 값 200은 성공을 의미해요. |
부분 응답 (Partial responses)
실행 중 하나 이상의 샤드가 실패해도 multi-search API는 성공한 샤드의 결과를 계속 반환해요. responses 배열의 각 개별 검색 응답에는 몇 개의 샤드가 성공하고 실패했는지 보고하는 _shards 객체가 포함되어 있어, 결과가 완전한지 판단할 수 있어요.
필요한 권한 (Required permissions)
Security 플러그인을 사용한다면 적절한 권한이 있는지 확인해야 해요: indices:data/read/msearch.