Dynamic templates
출처: 문서
본문
다이내믹 템플릿
다이내믹 템플릿은 OpenSearch가 다이내믹 매핑을 통해 추가하는 새 필드에 적용하는 매핑을 정의해요. 템플릿이 없으면 OpenSearch는 JSON 값을 기준으로 각 새 필드에 기본 타입을 할당하는데, 예를 들어 문자열에는 keyword 서브필드를 가진 text를, 정수에는 long을 할당해요. 다이내믹 템플릿은 매칭되는 필드에 대해서는 이런 기본값을 대체해요.
모든 필드를 미리 나열할 수는 없지만 특정 필드 그룹을 어떻게 매핑해야 하는지 알고 있을 때 다이내믹 템플릿을 사용하세요. 예를 들어 이름이 _id로 끝나는 모든 필드를 keyword로 매핑하거나, 공간을 절약하려고 정수를 integer로 저장하거나, 객체 안의 모든 필드를 하나의 검색 필드로 복사할 수 있어요.
템플릿은 다음 기준 중 하나 이상으로 필드를 선택해요:
- 값에 대해 OpenSearch가 감지한 JSON 데이터 타입.
match_mapping_type으로 지정해요 - 필드 이름.
match와unmatch로 지정해요 address.city같은 문서 내에서의 필드 위치.path_match와path_unmatch로 지정해요
다이내믹 템플릿은 아직 매핑에 없는 필드에만 적용돼요. 이미 매핑된 필드는 바꾸지 않아요. dynamic 파라미터를 strict_allow_templates나 false_allow_templates로 설정하면 OpenSearch는 다이내믹 템플릿에 매칭되는 필드만 추가해요.
템플릿 구조
인덱스 매핑의 dynamic_templates 배열에 다이내믹 템플릿을 정의해요. 각 배열 요소는 이름 붙은 템플릿 하나를 담은 객체예요:
"dynamic_templates": [
{
"<template_name>": {
"match_mapping_type": "<detected_type>",
"match": "<field_name_pattern>",
"mapping": {
"type": "<field_type>"
}
}
}
]
템플릿 이름은 직접 정해요. OpenSearch는 매핑을 반환할 때와 오류를 보고할 때 이 이름으로 템플릿을 식별해요. 그러니 템플릿이 대상으로 삼는 필드를 잘 설명하는 이름을 고르세요.
다이내믹 템플릿 파라미터
다음 표는 템플릿 파라미터를 정리한 거예요.
| 파라미터 | 설명 |
|---|---|
match_mapping_type |
매칭할 감지된 JSON 데이터 타입이에요. 모든 타입을 매칭하려면 *를 사용하세요. 유효한 값은 데이터 타입 매칭을 참고하세요. |
match |
필드 이름이 매칭해야 하는 패턴이에요. 패턴은 필드 이름에만 적용되고, 전체 경로에는 적용되지 않아요. |
unmatch |
match가 선택한 필드 이름을 제외하는 패턴이에요. |
match_pattern |
OpenSearch가 match, unmatch, path_match, path_unmatch의 패턴을 해석하는 방식을 지정해요. 유효한 값은 다음과 같아요:- simple: 패턴이 * 와일드카드를 지원해요. - regex: 패턴이 Java 정규 표현식이에요. 기본값은 simple이에요. |
path_match |
필드까지의 점으로 구분된 전체 경로가 매칭해야 하는 패턴이에요. 예를 들어 address.*가 있어요. |
path_unmatch |
path_match가 선택한 경로를 제외하는 패턴이에요. |
mapping |
필수예요. 매칭되는 필드에 적용할 매핑이에요. {name}과 {dynamic_type} 템플릿 변수를 포함할 수 있어요. |
다이내믹 템플릿 규칙
다이내믹 템플릿을 정의할 때는 다음 규칙을 유의하세요:
- 각 템플릿은
match_mapping_type,match,path_match중 최소 하나를 포함해야 해요.unmatch나path_unmatch만 포함한 템플릿은 거부돼요. - 인식되지 않는 파라미터를 포함한 템플릿은 거부돼요.
- OpenSearch는 배열 순서대로 템플릿을 검사하고 필드에 매칭되는 첫 번째 템플릿을 적용해요. 더 구체적인 템플릿을 더 일반적인 템플릿보다 앞에 두세요.
- OpenSearch는 필드가 처음 값을 가질 때 매핑에 새 필드를 추가해요. 값이
null이거나 빈 배열([])인 필드는 매핑되지 않고, 템플릿 평가도 이루어지지 않아요. 템플릿이null_value를 설정한 경우에도, 그 설정은 필드가 처음 매핑되기 전에 인덱싱된 문서에는 영향을 주지 않아요.
OpenSearch는 매핑을 만들거나 업데이트할 때 템플릿의 패턴을 검사하지만, mapping의 내용까지 완전히 검증하지는 않아요. 예를 들어 존재하지 않는 필드 타입을 지정한 템플릿은 수용되고, OpenSearch는 deprecation 로그에 경고를 기록해요. 템플릿은 매칭되는 필드가 인덱싱될 때 비로소 실패하고, 그 시점에 OpenSearch는 문서를 거부해요. {name} 변수를 포함한 템플릿은 필드 이름을 알 수 없어서 사전에 검증할 수 없어요. 프로덕션에 사용하기 전에 샘플 문서를 인덱싱해서 새 템플릿을 테스트하세요.
{: .note}
match_mapping_type이 없는 템플릿은 어떤 타입의 값이든 받을 수 있지만, 그 mapping이 일부 타입에만 유효할 수도 있어요. 매핑이 최소 하나의 감지된 타입에 유효하다면 OpenSearch는 그런 템플릿을 수용해요. 예를 들어 다음 템플릿은 type을 생략해서 OpenSearch가 감지된 타입을 사용하게 하고, text 필드만 받을 수 있는 analyzer를 설정해요:
PUT /dt-no-type
{
"mappings": {
"dynamic_templates": [
{
"code_fields": {
"match": "*_code",
"mapping": {
"analyzer": "simple"
}
}
}
]
}
}
{% include copy-curl.html %}
"country_code": "US"를 담은 문서는 인덱싱되고, country_code는 simple 애널라이저를 사용하는 text로 매핑돼요. "zip_code": 97205를 담은 문서는 long 필드가 애널라이저를 받지 않기 때문에 거부돼요. 템플릿을 문자열 값에만 적용하려면 "match_mapping_type": "string"을 추가하세요.
다이내믹 템플릿 업데이트
기존 인덱스의 템플릿을 바꾸려면 인덱스 매핑 생성 또는 업데이트 API를 사용해서 완전한 dynamic_templates 배열을 보내세요. 새 배열은 기존 템플릿을 모두 대체하기 때문에, 이 요청으로 템플릿을 재정렬하거나 제거할 수 있어요. 모든 템플릿을 제거하려면 빈 배열을 보내세요. 이전 템플릿이 이미 매핑한 필드는 자기 매핑을 유지해요.
데이터 타입 매칭
match_mapping_type 파라미터는 OpenSearch가 JSON 값에서 감지한 데이터 타입을 매칭해요. JSON은 정수 크기나 부동소수점 정밀도를 구분하지 않기 때문에, OpenSearch는 가장 넓은 타입을 감지해요. 다음 표는 match_mapping_type에 지정할 수 있는 값을 정리한 거예요.
| 값 | 매칭 대상 |
|---|---|
string |
날짜로 감지되지 않은 문자열 값이에요. |
long |
소수 부분이 없는 숫자예요. |
double |
소수 부분이 있는 숫자예요. |
boolean |
true와 false 값이에요. |
date |
dynamic_date_formats의 형식에 매칭되는 문자열이에요. date_detection이 활성화되어 있어야 하는데, 이게 기본값이에요. 자세한 내용은 날짜 감지를 참고하세요. |
object |
JSON 객체예요. |
binary |
바이너리 값이에요. JSON은 바이너리 값을 담을 수 없기 때문에, 이 타입은 CBOR, SMILE 같은 바이너리 콘텐츠 형식에만 적용돼요. |
knn_vector |
벡터로 매핑할 수 있는 숫자 배열이에요. 자세한 내용은 k-NN 벡터 다이내믹 템플릿을 참고하세요. |
* |
모든 데이터 타입이에요. |
감지된 타입이 매핑될 타입을 결정하지는 않아요. 예를 들어 long에 매칭되는 템플릿이 필드를 integer로 매핑할 수 있고, string에 매칭되는 템플릿이 필드를 long으로 매핑할 수도 있어요.
예제: 정수를 integer로 매핑하기
템플릿 두 개를 담은 인덱스를 생성해요. 첫 번째는 정수를 integer로 매핑하고, 두 번째는 문자열을 exact keyword 서브필드를 가진 text로 매핑해요:
PUT /dt-orders
{
"mappings": {
"dynamic_templates": [
{
"whole_numbers": {
"match_mapping_type": "long",
"mapping": {
"type": "integer"
}
}
},
{
"strings_with_exact": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"fields": {
"exact": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
]
}
}
{% include copy-curl.html %}
여러 타입의 값을 담은 문서를 인덱스해요:
PUT /dt-orders/_doc/1
{
"item_count": 3,
"customer_name": "Ana Rivera",
"total": 42.5,
"gift": true,
"order_date": "2026-09-30",
"empty_list": [],
"note": null
}
{% include copy-curl.html %}
매핑을 조회해요:
GET /dt-orders/_mapping
{% include copy-curl.html %}
응답에서 item_count는 integer로, customer_name은 exact 서브필드를 가진 text로 매핑돼요. total, gift, order_date 필드는 템플릿에 매칭되지 않아서 기본 다이내믹 매핑을 받아요. empty_list와 note 필드는 구체적인 값을 담고 있지 않아서 매핑에 추가되지 않아요:
{
"dt-orders": {
"mappings": {
"dynamic_templates": [
{
"whole_numbers": {
"match_mapping_type": "long",
"mapping": {
"type": "integer"
}
}
},
{
"strings_with_exact": {
"match_mapping_type": "string",
"mapping": {
"fields": {
"exact": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
}
],
"properties": {
"customer_name": {
"type": "text",
"fields": {
"exact": {
"type": "keyword",
"ignore_above": 256
}
}
},
"gift": {
"type": "boolean"
},
"item_count": {
"type": "integer"
},
"order_date": {
"type": "date"
},
"total": {
"type": "float"
}
}
}
}
}
필드 이름 매칭
match 파라미터는 이름으로 필드를 선택하고, unmatch는 그 선택에서 필드를 제외해요. 두 파라미터 모두 필드 이름에 대해서만 비교해요. 예를 들어 "match": "city"는 최상위 city 필드와 객체 안의 store.city 필드 모두에 매칭돼요.
예제: 필드 이름으로 숫자 문자열 매핑하기
데이터 소스 중에는 숫자를 문자열로 보내는 경우도 있어요. 이름이 reading_으로 시작하는 문자열 필드를 double로 매핑하되, 이름이 _label로 끝나는 필드는 제외하는 인덱스를 생성해요:
PUT /dt-sensor
{
"mappings": {
"dynamic_templates": [
{
"numeric_strings": {
"match_mapping_type": "string",
"match": "reading_*",
"unmatch": "*_label",
"mapping": {
"type": "double"
}
}
}
]
}
}
{% include copy-curl.html %}
두 종류의 필드를 모두 담은 문서를 인덱스해요:
PUT /dt-sensor/_doc/1
{
"reading_temp": "21.7",
"reading_label": "lobby"
}
{% include copy-curl.html %}
매핑을 조회해요:
GET /dt-sensor/_mapping
{% include copy-curl.html %}
reading_temp 필드는 double로 매핑돼요. reading_label 필드는 unmatch 때문에 제외되므로 기본 문자열 매핑을 받아요:
{
"dt-sensor": {
"mappings": {
...
"properties": {
"reading_label": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"reading_temp": {
"type": "double"
}
}
}
}
}
예제: 정규 표현식으로 필드 이름 매칭하기
match_pattern을 regex로 설정하면 패턴을 Java 정규 표현식으로 작성할 수 있어요. revenue_q1부터 revenue_q4까지의 필드만 scaled_float로 매핑하는 인덱스를 생성해요:
PUT /dt-regex
{
"mappings": {
"dynamic_templates": [
{
"quarterly_revenue": {
"match_pattern": "regex",
"match": "^revenue_q[1-4]$",
"mapping": {
"type": "scaled_float",
"scaling_factor": 100
}
}
}
]
}
}
{% include copy-curl.html %}
매출 필드 세 개를 담은 문서를 인덱스해요:
PUT /dt-regex/_doc/1
{
"revenue_q1": 1250.75,
"revenue_q5": 10.5,
"revenue_total": 5000.1
}
{% include copy-curl.html %}
매핑을 조회해요:
GET /dt-regex/_mapping
{% include copy-curl.html %}
정규 표현식에 매칭되는 것은 revenue_q1뿐이에요. 나머지 두 필드는 기본 float 매핑을 받아요:
{
"dt-regex": {
"mappings": {
...
"properties": {
"revenue_q1": {
"type": "scaled_float",
"scaling_factor": 100.0
},
"revenue_q5": {
"type": "float"
},
"revenue_total": {
"type": "float"
}
}
}
}
}
패턴이 유효한 정규 표현식이 아니면 OpenSearch는 매핑을 거부해요.
필드 경로 매칭
path_match와 path_unmatch 파라미터는 match와 unmatch처럼 동작하지만, 패턴을 모든 부모 객체 이름과 필드 이름을 마침표로 연결한 문자열과 비교해요. 예를 들어 address 객체 안 city 필드의 경로는 address.city예요. 특정 객체 안의 필드를 선택할 때 사용하세요.
예제: 객체 필드를 단일 필드로 복사하기
address 객체의 모든 필드를 text로 매핑하고 그 값을 최상위 full_address 필드로 복사하는 인덱스를 생성해요. zip_code 필드는 제외돼요:
PUT /dt-contacts
{
"mappings": {
"dynamic_templates": [
{
"address_lines": {
"path_match": "address.*",
"path_unmatch": "*.zip_code",
"mapping": {
"type": "text",
"copy_to": "full_address"
}
}
}
]
}
}
{% include copy-curl.html %}
address 객체를 담은 문서를 인덱스해요:
PUT /dt-contacts/_doc/1?refresh=true
{
"address": {
"street": "221 Harbor Lane",
"city": "Portland",
"zip_code": "97205"
}
}
{% include copy-curl.html %}
매핑을 조회해요:
GET /dt-contacts/_mapping
{% include copy-curl.html %}
street과 city 필드는 템플릿으로 매핑되고, zip_code는 기본 문자열 매핑을 받아요. full_address 필드는 문서에 등장하지 않기 때문에, OpenSearch는 값이 처음 복사될 때 매핑에 추가해요:
{
"dt-contacts": {
"mappings": {
...
"properties": {
"address": {
"properties": {
"city": {
"type": "text",
"copy_to": [
"full_address"
]
},
"street": {
"type": "text",
"copy_to": [
"full_address"
]
},
"zip_code": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"full_address": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
이제 쿼리 한 번으로 두 줄의 주소를 모두 검색할 수 있어요:
GET /dt-contacts/_search
{
"query": {
"match": {
"full_address": "harbor portland"
}
}
}
{% include copy-curl.html %}
path_match 패턴은 리프 필드뿐 아니라 객체에도 매칭돼요. 이 예제에서 address.*는 address 안에 중첩된 객체에도 매칭되고, OpenSearch는 그 객체를 text로 매핑하려고 시도해요. address 안에 unit 객체를 담은 문서를 인덱스해요:
PUT /dt-contacts/_doc/2
{
"address": {
"street": "9 Elm Court",
"unit": {
"number": "4B",
"floor": 2
}
}
}
{% include copy-curl.html %}
객체는 text 값으로 파싱될 수 없기 때문에 OpenSearch는 문서를 거부해요. 템플릿이 객체에 매칭하지 않도록 하려면 "match_mapping_type": "string"을 추가하세요.
템플릿 변수
mapping 객체에는 다음 변수들을 넣을 수 있고, OpenSearch는 템플릿을 필드에 적용할 때 이 변수들을 치환해요:
{name}: 필드 이름{dynamic_type}: 필드의 감지된 데이터 타입. 예를 들면long이나boolean
예제: 매핑에서 필드 이름과 감지된 타입 사용하기
템플릿 두 개를 담은 인덱스를 생성해요. 첫 번째는 각 문자열 필드를 text로 매핑하고 필드 이름을 애널라이저 이름으로 사용해요. 두 번째는 나머지 모든 필드에 적용되며, 감지된 타입을 유지하고 doc values를 비활성화해요:
PUT /dt-placeholders
{
"mappings": {
"dynamic_templates": [
{
"language_analyzers": {
"match_mapping_type": "string",
"match": "*",
"mapping": {
"type": "text",
"analyzer": "{name}"
}
}
},
{
"detected_type_without_doc_values": {
"match_mapping_type": "*",
"mapping": {
"type": "{dynamic_type}",
"doc_values": false
}
}
}
]
}
}
{% include copy-curl.html %}
내장 언어 애널라이저의 이름을 따서 지은 필드를 담은 문서를 인덱스해요:
PUT /dt-placeholders/_doc/1
{
"english": "Running shoes for trail use",
"french": "Chaussures de course",
"views": 120
}
{% include copy-curl.html %}
매핑을 조회해요:
GET /dt-placeholders/_mapping
{% include copy-curl.html %}
english와 french 필드는 같은 이름의 애널라이저를 사용하고, views는 doc values가 비활성화된 상태로 long으로 매핑돼요:
{
"dt-placeholders": {
"mappings": {
...
"properties": {
"english": {
"type": "text",
"analyzer": "english"
},
"french": {
"type": "text",
"analyzer": "french"
},
"views": {
"type": "long",
"doc_values": false
}
}
}
}
}
첫 번째 템플릿은 모든 문자열 필드에 매칭되기 때문에, 이름이 애널라이저 이름이 아닌 문자열 필드가 있으면 OpenSearch가 문서를 거부해요. 예를 들어 description 필드를 담은 문서는 description이라는 이름의 애널라이저가 없기 때문에 거부돼요.
일반적인 템플릿 패턴
다음 예제들은 일반적인 인덱싱 요구 사항에 대한 템플릿을 보여줘요.
메트릭의 인덱스 크기 줄이기
메트릭 문서는 보통 집계와 대시보드에서 사용되지만 쿼리 필터에는 거의 나타나지 않는 많은 숫자 필드를 포함해요. 이러한 필드를 index를 false로 설정하여 매핑하면 검색 인덱스 구축을 건너뛰어 디스크 공간과 인덱싱 시간을 절약할 수 있어요. Doc values는 여전히 기록되므로 집계와 정렬에는 영향을 주지 않아요. OpenSearch가 숫자 doc values 검색으로 대체(fallback)하기 때문에 필드를 계속 쿼리할 수 있지만, 이러한 쿼리는 인덱스된 필드에 대한 쿼리보다 느리게 실행돼요.
PUT /dt-metrics
{
"mappings": {
"dynamic_templates": [
{
"metric_counters": {
"match_mapping_type": "long",
"mapping": {
"type": "long",
"index": false
}
}
},
{
"metric_gauges": {
"match_mapping_type": "double",
"mapping": {
"type": "float",
"index": false
}
}
}
]
}
}
{% include copy-curl.html %}
metric_gauges 템플릿은 동적 매핑이 기본적으로 소수점 숫자에 할당하는 것과 같은 float 타입을 사용해요. float는 double의 8바이트에 비해 값당 4바이트를 차지하며, 로드 평균이나 백분율 같은 값에는 정밀도가 충분해요.
식별자 필드를 keyword로 매핑하기
매장 번호나 주문 번호와 같은 식별자는 정확히 일치하며, 숫자여도 범위 쿼리나 계산에 사용되지 않아요. 다음 템플릿은 값이 문자열이든 숫자든 관계없이 이름이 _id로 끝나는 모든 필드를 keyword로 매핑해요.
PUT /dt-id-fields
{
"mappings": {
"dynamic_templates": [
{
"identifiers": {
"match": "*_id",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
{% include copy-curl.html %}
"store_id": 17과 "order_id": "A-1001"을 포함하는 문서는 두 필드를 모두 keyword로 매핑해요. 템플릿이 없으면 store_id는 long으로 매핑될 거예요. 템플릿이 match_mapping_type을 지정하지 않기 때문에 이름이 _id로 끝나는 객체도 일치해요. OpenSearch는 객체를 keyword로 매핑할 수 없기 때문에 그러한 객체를 포함하는 문서를 거부해요. 문서에 이러한 객체가 포함되어 있다면, 동일한 match 패턴을 가진 두 개의 템플릿을 정의하세요. 하나는 "match_mapping_type": "string"이고 다른 하나는 "match_mapping_type": "long"이에요. 그러면 객체는 두 템플릿 모두 일치하지 않으며 object 필드로 매핑돼요.
문자열을 keyword로만 매핑하기
문자열 필드를 정확한 값으로만 검색하고 필터링, 정렬, 집계에 사용한다면 text 필드 없이 keyword로 매핑하여 전체 텍스트 인덱싱을 건너뛸 수 있어요. 그러면 쿼리가 인덱스된 전체 값과 일치해야 하므로 Green을 검색해도 Forest Green 값은 일치하지 않아요.
PUT /dt-keyword-only
{
"mappings": {
"dynamic_templates": [
{
"exact_value_strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
{% include copy-curl.html %}
자유 형식 객체를 flat object로 매핑하기
객체가 서로 다른 판매자가 제공하는 제품 속성처럼 문서마다 다른 키를 포함하는 경우, 각각의 새 키가 매핑에 필드를 추가하여 매핑 폭발로 이어질 수 있어요. 다음 템플릿은 이름이 _attributes로 끝나는 모든 객체를 단일 flat_object 필드로 매핑해요. 다른 객체는 정상적으로 매핑돼요.
PUT /dt-flat-attributes
{
"mappings": {
"dynamic_templates": [
{
"free_form_objects": {
"match_mapping_type": "object",
"match": "*_attributes",
"mapping": {
"type": "flat_object"
}
}
}
]
}
}
{% include copy-curl.html %}
product_attributes 객체는 포함된 키 수에 관계없이 하나의 flat_object 필드로 매핑되며, 점 표기법(예: product_attributes.dimensions.width)을 사용하여 하위 필드를 계속 쿼리할 수 있어요.
문자열 필드에서 norms 비활성화하기
Norms는 OpenSearch가 관련성 점수 매기기에만 사용하는 필드 길이 정보를 저장해요. 로그 메시지와 같은 데이터의 경우 다른 필드로 필터링하고 정렬하며 점수로 결과를 순위 매기지 않기 때문에 norms를 비활성화하면 인덱스 크기가 줄어들어요. 다음 템플릿은 문자열을 norms 없이 text로 매핑하고 동적 매핑이 기본적으로 추가하는 keyword 하위 필드를 유지해요.
PUT /dt-no-norms
{
"mappings": {
"dynamic_templates": [
{
"unscored_text": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"norms": false,
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
]
}
}
{% include copy-curl.html %}