Terms 쿼리

Terms 쿼리

같은 필드에서 여러 용어를 검색하려면 terms 쿼리를 사용해요. 이 글에서는 기본적인 terms 쿼리, terms lookup, 쿼리 기반 terms lookup, 비트맵 필터링을 살펴봐요.

출처: 문서

본문

같은 필드에서 여러 용어를 검색하려면 terms 쿼리를 사용해요. 예를 들어 다음 쿼리는 ID가 61809와 61810인 행을 검색해요:

GET shakespeare/_search
{
  "query": {
    "terms": {
      "line_id": [
        "61809",
        "61810"
      ]
    }
  }
}

배열 안에 있는 용어 중 하나라도 일치하면 문서가 반환돼요. 기본적으로 terms 쿼리에서 허용되는 최대 용어 수는 65,536이에요. 최대 용어 수를 변경하려면 index.max_terms_count 설정을 업데이트하세요. 쿼리 성능을 높이려면 용어를 정렬된 순서(UTF-8 바이트 값 오름차순)로 포함한 긴 배열을 전달하세요. terms 쿼리 결과에 대한 하이라이팅 가능 여부는 하이라이터 타입과 쿼리의 용어 수에 따라 보장되지 않을 수 있어요.

파라미터 (Parameters)

쿼리는 다음 파라미터를 받아요. 모든 파라미터는 선택이에요. Parameter Data type Description <field> | 문자열(String) | 검색할 필드예요. 필드 값이 올바른 공백과 대소문자로 최소 하나의 용어와 정확히 일치하는 문서만 결과에 반환돼요. boost | 부동 소수점(Floating-point) | 이 필드가 관련성 점수에 기여하는 가중치를 지정하는 부동 소수점 값이에요. 1.0보다 큰 값은 필드의 관련성을 높이고, 0.0과 1.0 사이의 값은 관련성을 낮춰요. 기본값은 1.0이에요. _name | 문자열(String) | 쿼리 태깅(query tagging)을 위한 쿼리 이름이에요. 선택이에요. value_type | 문자열(String) | 필터링에 사용되는 값의 타입을 지정해요. 유효한 값은 default와 bitmap이에요. 생략하면 기본값 default가 사용돼요.

Terms lookup (용어 조회)

terms lookup은 단일 문서의 필드 값을 가져와 검색 용어로 사용해요. terms lookup을 사용해 많은 수의 용어를 검색할 수 있어요. terms lookup을 사용하려면 _source 매핑 필드를 활성화해야 해요. terms lookup은 문서에서 값을 가져오기 때문이에요. _source 필드는 기본적으로 활성화되어 있어요. terms lookup은 로컬 데이터 노드의 샤드에서 문서 필드 값을 가져오려고 해요. 따라서 모든 관련 데이터 노드에 전체 복제본이 있는 단일 기본 샤드를 가진 인덱스를 사용하면 네트워크 트래픽이 줄어들어요.

예제 (Example)

예시로, student_id를 keyword로 매핑한 학생 데이터가 담긴 인덱스를 생성해요:

PUT students
{
  "mappings": {
    "properties": {
      "student_id": { "type": "keyword" }
    }
  }
}

다음으로 학생에 해당하는 세 문서를 인덱싱하세요:

PUT students/_doc/1
{
  "name": "Jane Doe",
  "student_id" : "111"
}

PUT students/_doc/2
{
  "name": "Mary Major",
  "student_id" : "222"
}

PUT students/_doc/3
{
  "name": "John Doe",
  "student_id" : "333"
}

수업 이름과 수업에 등록된 학생에 해당하는 학생 ID 배열을 포함한 클래스 정보가 담긴 별도의 인덱스를 생성하세요:

PUT classes/_doc/101
{
  "name": "CS101",
  "enrolled" : ["111" , "222"]
}

CS101 수업에 등록된 학생을 검색하려면, 수업에 해당하는 문서의 문서 ID, 해당 문서의 인덱스, 그리고 용어가 위치한 필드의 경로를 지정하세요:

GET students/_search
{
  "query": {
    "terms": {
      "student_id": {
        "index": "classes",
        "id": "101",
        "path": "enrolled"
      }
    }
  }
}

응답에는 ID가 enrolled 배열의 값 중 하나와 일치하는 모든 학생에 대해 students 인덱스의 문서가 포함돼요:

{
  "took": 13,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "students",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "Jane Doe",
          "student_id": "111"
        }
      },
      {
        "_index": "students",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "Mary Major",
          "student_id": "222"
        }
      }
    ]
  }
}

예제: 중첩 필드 (Nested fields)

두 번째 예제는 중첩 필드를 쿼리하는 방법을 보여줘요. 다음 문서를 가진 인덱스를 생각해 봐요:

PUT classes/_doc/102
{
  "name": "CS102",
  "enrolled_students" : {
    "id_list" : ["111" , "333"]
  }
}

CS102에 등록된 학생을 검색하려면 path 파라미터에 필드의 전체 경로를 점 표기법(dot path notation)으로 지정하세요:

GET students/_search
{
  "query": {
    "terms": {
      "student_id": {
        "index": "classes",
        "id": "102",
        "path": "enrolled_students.id_list"
      }
    }
  }
}

응답에는 일치하는 문서가 포함돼요:

{
  "took": 18,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "students",
        "_id": "1",
        "_score": 1,
        "_source": {
          "name": "Jane Doe",
          "student_id": "111"
        }
      },
      {
        "_index": "students",
        "_id": "3",
        "_score": 1,
        "_source": {
          "name": "John Doe",
          "student_id": "333"
        }
      }
    ]
  }
}

파라미터 (Parameters)

다음 표는 terms lookup 파라미터를 정리한 것이에요. Parameter Data type Description index | 문자열(String) | 필드 값을 가져올 인덱스의 이름이에요. 필수예요. id | 문자열(String) | 필드 값을 가져올 문서의 문서 ID예요. 필수예요. query | 객체(Object) | 필드 값을 가져올 여러 문서를 선택하는 데 사용하는 쿼리 객체예요. id가 제공되지 않으면 필수예요. path | 문자열(String) | 필드 값을 가져올 필드의 이름이에요. 중첩 필드는 점 표기법으로 지정해요. 필수예요. routing | 문자열(String) | 필드 값을 가져올 문서의 사용자 지정 라우팅(routing) 값이에요. 선택이에요. 문서를 인덱싱할 때 사용자 지정 라우팅 값을 제공했다면 필수예요. store | 불리언(Boolean) | _source 대신 저장된(stored) 필드에서 조회를 수행할지 여부예요. 선택이에요.

쿼리 기반 terms lookup

3.2 버전에서 도입되었어요. 쿼리를 사용해 여러 문서에서 값을 동적으로 추출해 terms 쿼리에 사용할 수 있어요. 문서 ID를 지정하는 대신, query 파라미터로 문서를 일치시키고 그 일치 항목들에서 지정된 필드의 모든 값을 수집할 수 있어요. 이 기능은 다른 인덱스의 문서에 있는 필드 값을 기준으로 한 인덱스를 검색하고 싶을 때 유용해요. 지원되는 파라미터 목록은 terms lookup parameters를 참고하세요. 쿼리 기반 terms lookup을 사용하려면 terms lookup 객체에서 id 대신 query 파라미터를 제공해야 해요.

값이 수집되는 방식 (How values are collected)

terms lookup의 동작은 대상 필드가 일치하는 문서에서 어떻게 나타나는지에 따라 달라요:

  • 쿼리와 일치하는 문서가 지정된 필드를 포함하지 않으면, 그 문서는 용어 추출에서 무시돼요.
  • 필드가 목록(list)이면 모든 항목이 수집돼요.
  • 필드가 스칼라(scalar)이면 그 값이 수집돼요.
  • 같은 필드가 서로 다른 문서에서 단일 값이거나 목록이면, 모든 값이 단일 목록으로 평탄화(flatten)되어 중복이 제거돼요.
  • 여러 목록이 단일 목록으로 평탄화돼요.
  • 필드가 없거나 null이거나 빈 목록이면 건너뛰어요.
  • 여러 문서의 중복 값은 제거돼요.
  • 쿼리와 일치하는 문서가 없으면 terms 쿼리는 값이 지정되지 않은 것처럼 동작해요 (일반적으로 아무것도 일치하지 않아요).
  • 일치하는 문서 중 어느 것도 필드를 포함하지 않으면 쿼리는 아무것도 일치시키지 않아요.

예제 (Example)

먼저 사용자 정보가 담긴 users라는 인덱스를 생성하세요:

PUT /users
{
  "mappings": {
    "properties": {
      "username": { "type": "keyword" }
    }
  }
}

인덱스에 사용자 데이터를 추가하세요:

PUT users/_doc/u1
{ "username": "alice" }

PUT users/_doc/u2
{ "username": "bob" }

PUT users/_doc/u3
{ "username": "carol" }

PUT users/_doc/u4
{ "username": "dave" }

다음으로 그룹 멤버십을 담은 인덱스를 생성하세요:

PUT groups
{
  "mappings": {
    "properties": {
      "group": { "type": "keyword" },
      "members": { "type": "keyword" }
    }
  }
}

인덱스에 그룹 멤버십 데이터를 추가하세요:

PUT groups/_doc/1
{
  "group": "g1",
  "members": ["alice", "bob"]
}

PUT groups/_doc/2
{
  "group": "g1",
  "members": "carol"
}

PUT groups/_doc/3
{
  "group": "g1"
}

PUT groups/_doc/4
{
  "group": "g1",
  "members": []
}

PUT groups/_doc/5
{
  "group": "g1",
  "members": null
}

PUT groups/_doc/6
{
  "group": "g2",
  "members": "carol"
}

g1 그룹의 모든 멤버인 사용자를 users 인덱스에서 검색하려면 다음 요청을 사용하세요:

GET /users/_search
{
  "query": {
    "terms": {
      "username": {
        "index": "groups",
        "path": "members",
        "query": {
          "term": { "group": "g1" }
        }
      }
    }
  }
}

이 쿼리는 group이 g1으로 설정된 groups의 문서에서 members 필드의 모든 값을 수집해, users 인덱스의 username 필드에 대한 용어로 사용해요:

{
  "hits": {
    "total": { "value": 3, "relation": "eq" },
    "hits": [
      { "_index": "users", "_id": "u1", "_score": 1.0, "_source": { "username": "alice" } },
      { "_index": "users", "_id": "u2", "_score": 1.0, "_source": { "username": "bob" } },
      { "_index": "users", "_id": "u3", "_score": 1.0, "_source": { "username": "carol" } }
    ]
  }
}

이 쿼리는 일치하는 문서를 다음과 같이 처리해요:

  • 조회 쿼리는 문서 1, 2, 3, 4, 5와 일치해요 (모두 group g1을 지정해요).
  • 문서 6(다른 그룹 g2 사용)은 쿼리에서 무시돼요.
  • 각 일치 문서의 members 필드는 다음과 같이 처리돼요: 문서 1: ["alice", "bob"] (목록) → alice와 bob이 모두 수집돼요.
  • 문서 2: "carol" (스칼라) → carol이 수집돼요.
  • 문서 3: members 필드 없음 → 무시돼요.
  • 문서 4: 빈 목록 → 무시돼요.
  • 문서 5: null → 무시돼요.

수집된 모든 값은 평탄화되고 중복이 제거되어, 최종 결과는 ["alice", "bob", "carol"]이에요.

비트맵 필터링 (Bitmap filtering)

2.17 버전에서 도입되었어요. terms 쿼리는 여러 용어를 동시에 필터링할 수 있어요. 하지만 입력 필터의 용어 수가 많은 값(약 10,000개)으로 늘어나면 그에 따른 네트워크와 메모리 오버헤드가 커져 쿼리가 비효율적이 될 수 있어요. 이런 경우 큰 용어 필터를 roaring bitmap으로 인코딩해 더 효율적으로 필터링하는 것을 고려해 보세요. 다음 예제는 두 인덱스가 있다고 가정해요. 하나는 회사가 판매하는 모든 상품을 담은 products 인덱스이고, 다른 하나는 특정 상품을 보유한 고객을 나타내는 필터를 저장하는 customers 인덱스예요. 먼저 product_id를 정수(integer)로 매핑한 products 인덱스를 생성하세요:

PUT /products
{
  "mappings": {
    "properties": {
      "product_id": { "type": "integer" }
    }
  }
}

다음으로 상품에 해당하는 세 문서를 인덱싱하세요:

PUT /products/_doc/1
{
  "name": "Product 1",
  "product_id" : 111
}

PUT /products/_doc/2
{
  "name": "Product 2",
  "product_id" : 222
}

PUT /products/_doc/3
{
  "name": "Product 3",
  "product_id" : 333
}

고객 비트맵 필터를 저장하려면 customers 인덱스에 customer_filter binary 필드를 생성할 거예요. 필드를 저장하려면 store를 true로 지정하세요:

PUT /customers
{
  "mappings": {
    "properties": {
      "customer_filter": {
        "type": "binary",
        "store": true
      }
    }
  }
}

각 고객에 대해, 고객이 보유한 상품의 product ID를 나타내는 비트맵을 생성해야 해요. 이 비트맵은 해당 고객의 필터 기준을 효과적으로 인코딩해요. 이 예제에서는 ID가 customer123이고 상품 111, 222, 333을 보유한 고객에 대한 terms 필터를 만들 거예요. 고객의 terms 필터를 인코딩하려면 먼저 필터용 roaring bitmap을 생성하세요. 이 예제는 [PyRoaringBitMap] 라이브러리로 비트맵을 만들므로, 먼저 pip install pyroaring을 실행해 라이브러리를 설치하세요. 그런 다음 비트맵을 직렬화(serialize)하고 Base64 인코딩 방식으로 인코딩해요:

from pyroaring import BitMap
import base64

# Create a bitmap, serialize it into a byte string, and encode into Base64
bm = BitMap([111, 222, 333]) # product ids owned by a customer
encoded = base64.b64encode(BitMap.serialize(bm))

# Convert the Base64-encoded bytes to a string for storage or transmission
encoded_bm_str = encoded.decode('utf-8')

# Print the encoded bitmap
print(f"Encoded Bitmap: {encoded_bm_str}")

다음으로 고객 필터를 customers 인덱스에 인덱싱하세요. 필터의 문서 ID는 해당 고객의 ID와 같아요(이 예제에서는 customer123). customer_filter 필드에는 이 고객을 위해 생성한 비트맵이 들어 있어요:

POST customers/_doc/customer123
{
  "customer_filter": "OjAAAAEAAAAAAAIAEAAAAG8A3gBNAQ=="
}

이제 products 인덱스에서 terms 쿼리를 실행해 customers 인덱스의 특정 고객을 조회할 수 있어요. _source 대신 저장된(stored) 필드를 조회하므로 store를 true로 설정해요. value_type 필드에는 terms 입력의 데이터 타입을 bitmap으로 지정하세요:

POST /products/_search
{
  "query": {
    "terms": {
      "product_id": {
        "index": "customers",
        "id": "customer123",
        "path": "customer_filter",
        "store": true               
      },
      "value_type": "bitmap"      
    }
  }
}

비트맵을 terms 쿼리에 직접 전달할 수도 있어요. 이 예제에서 product_id 필드는 ID가 customer123인 고객의 고객 필터 비트맵을 담고 있어요:

POST /products/_search
{
  "query": {
    "terms": {
      "product_id": [
        "OjAAAAEAAAAAAAIAEAAAAG8A3gBNAQ=="
      ],
      "value_type": "bitmap"
    }
  }
}

Terms lookupExample

Terms lookup by queryHow values are collected

더 알아보기 (Learn more)