LLM WikiAccess-protected knowledge portal
← 스터디 홈
15편 · 약 20분

OpenSearch 3.7: 매핑 업데이트 없는 dynamic_properties, Parquet 인덱싱, WritableWarm tiering

왜 이번 릴리스를 봐야 하나

2026년 6월 9일 공개된 OpenSearch 3.7.0은 검색 기능 몇 개를 추가한 릴리스로 읽으면 핵심을 놓친다. 운영자 관점에서 더 중요한 변화는 다음 세 가지다.

  • dynamic_properties가 정식으로 쿼리 경로까지 연결됐다. 패턴에 맞는 필드를 인덱싱하면서도 클러스터 상태 매핑을 매번 갱신하지 않는다.
  • Pluggable data format 경로가 Parquet까지 확장됐다. DataFormatAwareEngineParquetDataFormatPlugin이 붙으면서 Lucene 외 포맷을 인덱싱 경로에 올리는 기반이 생겼다.
  • WritableWarm tiering이 API·상태·프리패치·쿼리 메트릭까지 한 덩어리로 들어왔다. 단순한 hot/warm 라우팅이 아니라, warm 노드에서 읽기 경로를 어떻게 버틸지까지 드러난다.

즉, 3.7은 “검색 엔진 기능 추가”보다 매핑 제어면, 세그먼트 포맷, 스토리지 계층화를 동시에 건드린 릴리스다. 이 글은 그 세 면이 실제 운영에서 어떤 의미를 갖는지에 집중한다.


변화가 걸리는 경로

OpenSearch 3.7 — 매핑, 포맷, warm tier를 한 번에 바꾼 릴리스 입력 / 질의 / 저장 계층 JSON 문서 · 패턴 기반 필드 · Lucene/Parquet 포맷 · hot/warm 노드 · remote segment store ① dynamic_properties 패턴 매칭으로 필드 타입 결정 cluster-state mapping update 없이 인덱싱 query 시 resolver가 fieldType 합성 효과: mapping update churn 감소 주의: 필드가 매핑 문서에 낱개로 남지 않음 제약: catch-all `*` 규칙은 허용되지 않음 ② DataFormatAwareEngine + Parquet Lucene 책임을 유지한 채 실행 엔진 분리 `parquet/` 하위 파일과 checksum 전략 분리 Parquet plugin이 columnar+bloom 지원 선언 효과: 다중 포맷 인덱싱 기반 주의: merge는 시스템 프로퍼티로 기본 OFF 실험적 surface, sandbox plugin 의존 ③ WritableWarm tiering `POST /{index}/_tier/warm`로 이동 시작 `GET /{index}/_tier`, `GET /_tier/all`로 상태 조회 prefetch, slow log, per-query metric 수집 효과: warm read 경로와 migration 상태가 가시화됨 주의: file cache/JVM headroom 없으면 요청 거절 실험적 API와 노드 역할 설계 필요 운영자가 실제로 점검할 것 1) 패턴 필드가 많을 때 mapping update·master 부담이 줄어드는 대신, 운영 도구가 필드를 어떻게 인식하는지 2) Parquet 경로를 켜더라도 merge·checksum·thread pool·remote switch가 아직 실험적 가정 위에 서 있는지 3) warm 노드의 file cache / JVM / remote store / rollback 경로가 없으면 tiering API가 실제로 안전하지 않다는 점
OpenSearch 3.7에서 운영자가 다시 봐야 할 세 경로

1. dynamic_properties: 패턴 필드를 받아도 매핑 갱신 왕복을 만들지 않는다

3.7 릴리스 노트의 첫 줄은 짧지만, 이 변화는 ingestion-heavy 클러스터에서 꽤 크다. DynamicProperty 구현은 다음을 분명하게 적는다.

  • 패턴에 맞는 필드는 같은 매핑 규칙으로 인덱싱된다.
  • 하지만 새 필드 정의를 cluster-state mapping에 추가하지 않는다.
  • 목적은 고처리량 인제스트에서 mapping update round-trip을 피하는 것이다.

실제 테스트도 같은 방향이다. DynamicMappingTests#testDynamicPropertiesNoMappingUpdatecount_i, name_s 같은 필드를 문서에 넣고도 doc.dynamicMappingsUpdate()null임을 검증한다. 즉, 패턴은 적용되지만 매번 PUT mapping에 해당하는 부수 효과는 없다.

{
  "dynamic_properties": {
    "*_i": { "type": "long" },
    "*_s": { "type": "keyword" }
  }
}

이 규칙은 ingest에서만 끝나지 않는다. DynamicPropertyFieldTypeResolver는 쿼리 시점에 패턴을 다시 보고 MappedFieldType을 합성한다. 구현을 보면:

  • resolver cache는 LRU 1024 entries다.
  • 같은 이름의 필드를 다시 질의하면 cache hit로 같은 MappedFieldType을 재사용한다.
  • 패턴 매칭은 leaf 이름만이 아니라 full dotted path 기준이다. 즉 obj.nested_i*_i에 걸릴 수 있다.

왜 운영자에게 중요할까

동적 필드가 매우 많은 로그/이벤트 워크로드는 다음 문제를 자주 만든다.

  1. 필드가 들어올 때마다 mapping update가 발생한다.
  2. cluster state가 커지고 master/cluster-manager의 부담이 올라간다.
  3. burst ingestion 동안 mapping propagation latency가 tail latency로 번진다.

dynamic_properties는 여기서 방향을 바꾼다. 필드를 개별 엔트리로 영구 등록하는 대신, 패턴을 계약으로 삼고 필드 타입을 계산한다. 운영자는 “필드 카탈로그를 완벽히 나열하는 편의”를 일부 포기하는 대신, 매핑 갱신 churn을 줄일 수 있다.

주의할 경계

소스는 몇 가지 경계를 명확히 남긴다.

  • * 단독 패턴은 허용되지 않는다. 모든 미지 필드에 단일 기본 규칙을 적용하려면 dynamic_templates를 써야 한다.
  • dynamic_propertiesobject 타입을 매핑해도, 쿼리용 fieldType이 자동으로 합성되지는 않는다.
  • 규칙은 full dotted path에 걸리므로, parent.child_i 같은 이름이 의도치 않게 매칭될 수 있다.

즉, 이 기능은 동적 필드 폭증을 눌러 주는 운영 장치이지, “매핑을 신경 안 써도 되는 마법”은 아니다. 패턴 설계가 조악하면 오히려 어디에 어떤 타입이 붙는지 추적하기 어려워질 수 있다.


2. Pluggable data format과 Parquet 경로: OpenSearch가 Lucene 외 세그먼트를 진지하게 다루기 시작했다

3.7의 두 번째 큰 변화는 data format 경로다. DataFormatAwareEngine의 클래스 설명은 이것이 단순 plugin hook이 아니라는 점을 보여 준다. 이 엔진은 Lucene과 분리되었지만 여전히 다음 책임을 직접 진다.

  • sequence number와 local checkpoint 추적
  • translog 관리와 recovery
  • refresh/flush로 searchability와 durability 보장
  • merge/backpressure 관리

즉, “파일 포맷만 바꾸는 얇은 래퍼”가 아니라 엔진 계약을 Lucene 고정에서 떼어 내는 첫 단계다.

Parquet plugin은 선언형 plugin이지만, 저장 경로까지 건드린다

ParquetDataFormatPlugin은 Parquet 포맷을 DataFormatPlugin으로 등록한다. 구현을 보면 세 가지가 핵심이다.

  1. parquet라는 포맷 이름을 등록한다.
  2. per-shard ParquetIndexingEngine을 만든다.
  3. tiered storage용 ParquetStoreStrategy를 함께 제공한다.

여기서 중요한 점은 이 plugin이 단순 포맷 인코더가 아니라 store 전략까지 등록한다는 것이다. 즉 Parquet 파일은 인덱싱 포맷이면서, warm/remote 계층에서 어떤 디렉터리와 checksum 전략을 쓸지도 함께 결정한다.

ParquetDataFormat 클래스는 Parquet 포맷이 지원하는 필드 capability를 COLUMNAR_STORAGEBLOOM_FILTER로 선언한다. 또한 plugin 경로가 sandbox/plugins/parquet-data-format에 있고, 클래스와 다수의 store/tiering 관련 구현에 @ExperimentalApi가 붙어 있다. 이건 운영자에게 매우 직접적인 메시지다.

3.7에서 Parquet는 "흥미로운 데모"를 넘어서기 시작했지만, 아직 일반 Lucene 인덱스처럼 투명한 기본 경로는 아니다.

파일 경로와 checksum 전략도 바뀐다

DataFormatAwareStoreDirectory는 파일 이름 규칙을 이렇게 설명한다.

  • Lucene: _0.cfs<shard>/index/_0.cfs
  • Parquet: parquet/_0_1.parquet<shard>/parquet/_0_1.parquet
  • Arrow: arrow/_0_1.arrow<shard>/arrow/_0_1.arrow

그리고 checksum도 포맷별로 다르다.

  • Lucene/index 파일은 codec footer checksum을 사용한다.
  • 비-Lucene 파일은 full-file CRC32를 사용한다.

이 차이는 실무적으로 중요하다. warm/remote 저장 계층에서 file integrity 확인 비용이 포맷별로 달라지기 때문이다. Parquet 파일이 늘어날수록 checksum 계산 경로와 복구 시간도 Lucene-only 가정과 달라진다.

merge support가 들어왔지만, 기본적으로는 아직 꺼져 있다

릴리스 노트는 “Parquet data format plugin에 streaming k-way merge sort 기반 merge support를 추가했다”고 적는다. 그런데 DataFormatAwareEngine 소스를 보면 더 중요한 문장이 있다.

MERGE_ENABLED_PROPERTY = opensearch.pluggable.dataformat.merge.enabled
기본값은 false, 모든 data format에서 merge 구현이 아직 완전하지 않기 때문

즉, release note만 보면 “Parquet merge가 준비됐다”고 읽기 쉽지만, 실제 운영 표면은 더 조심스럽다.

  • merge는 시스템 프로퍼티로 명시적으로 켜야 한다.
  • 이유는 모든 data format 구현이 아직 완전하지 않기 때문이다.
  • 따라서 canary 없이 production index에 바로 일반화하면 안 된다.

이 차이는 OpenSearch 3.7을 보는 가장 좋은 태도를 보여 준다. 릴리스는 방향을 열었지만, 기본 경로를 완전히 뒤집지는 않았다.

adoption 관점에서 무엇을 먼저 검증해야 하나

  • arrow-base plugin과 native allocator 의존성이 실제 배포 이미지에 들어 있는가
  • parquet_native_write thread pool이 node CPU budget과 충돌하지 않는가
  • Parquet 파일이 remote store로 넘어간 뒤 checksum/restore 시간이 허용 범위인가
  • merge flag를 켜지 않은 상태와 켠 상태의 disk/object churn 차이가 얼마나 나는가

3. WritableWarm tiering: API만 추가된 것이 아니라, warm 읽기 경로의 비용이 코드에 드러났다

3.7의 세 번째 축은 WritableWarm tiered storage다. 릴리스 노트는 이를 여러 항목으로 흩어 적지만, 소스를 모아 보면 하나의 설계 방향이 보인다.

  • index를 hot → warm / warm → hot으로 이동시키는 REST/API가 생겼다.
  • migration 상태를 index 단위 / 전체 목록으로 조회할 수 있다.
  • warm 읽기 경로를 위해 prefetch 설정과 query metric 수집기가 추가됐다.
  • tiered directory가 local → remote 전환 가능한 IndexInput 계층을 만든다.

즉, “warm 노드로 shard를 옮긴다”보다 warm에서 읽는 비용을 관리할 제어면이 생긴 것이다.

실제 API 표면

REST handler 구현 기준으로 3.7이 노출하는 기본 표면은 다음과 같다.

POST /{index}/_tier/warm
GET  /{index}/_tier
GET  /_tier/all

GET /{index}/_tierdetailed=true를 받을 수 있고, GET /_tier/alltarget=_hot 또는 target=_warm 필터를 받는다. TieringStatus 모델은 응답에 다음을 담는다.

  • index
  • state
  • source
  • target
  • start_time
  • 상세 모드일 때 shard-level status와 relocating node 정보

운영상 이건 “tiering을 눌렀는가”를 넘어 어느 샤드가 아직 pending/running인지까지 볼 수 있다는 뜻이다.

migration은 그냥 받아 주지 않는다

TieringUtilsHotToWarmTieringService를 보면, 3.7의 tiering은 여러 보호 장치를 둔다.

  • hot→warm 동시 요청 기본 한도: 200
  • warm→hot 동시 요청 기본 한도: 10
  • file cache active usage 임계치 기본값: 90%
  • JVM usage 임계치 기본값: 95%
  • .로 시작하는 메타데이터 계열 index는 기본 blocklist, 단 .ds-는 allowlist
  • REST 요청은 정확히 한 index만 받는다

그리고 hot→warm 시작 시 서비스는 index settings를 다음처럼 바꾼다.

index.warm = true
index.tiering.state = HOT_TO_WARM
index.composite.store_type = tiered-storage

이건 매우 중요한 시그널이다. 3.7의 warm tier는 단순 allocation label이 아니라 복합 스토어(composite store)와 remote path를 전제로 하는 저장 방식 전환이다.

warm read 비용을 가리는 것이 아니라 드러낸다

TieredStoragePrefetchSettings에는 기본값이 명시돼 있다.

tiering.service.prefetch.read_ahead.block_count = 4
tiering.service.prefetch.stored_fields.enabled = true

즉 warm tier는 기본적으로:

  • 일부 파일 형식(dvd)에 대해 read-ahead를 수행하고
  • stored fields prefetch를 켠 상태에서
  • query/fetch 단계의 prefetch 성공/실패를 계측한다

TieredStorageQueryMetricService는 thread/task/shard 단위 collector를 들고 있으며, stored fields prefetch와 doc values prefetch 성공/실패 카운터를 따로 기록한다. 소스에 남은 TODO를 보면 이 통계는 아직 node stats 표면으로 완전히 정리되지 않았을 가능성이 있다. 즉, 코드에는 계측이 들어왔지만 운영 배포가 어디까지 노출하는지는 실제 분포판에서 확인해야 한다.

remote 전환도 파일 단위로 조심스럽게 한다

FormatSwitchableIndexInput은 local input으로 시작했다가 remote metadata에 파일이 있다는 것이 확인되면 read path를 remote로 바꾼다. 구현상 특징은 다음과 같다.

  • switch 전에는 local file handle로 읽는다.
  • remote input 생성이 실패하면 상태를 그대로 유지한다.
  • clone/slice가 있으면 parent가 remote 전환 시 clone에도 cascade한다.
  • old local input close 실패는 로그만 남기고 switch를 완료한다.

즉 warm tier는 “파일을 원격에 올리고 끝”이 아니라, 읽기 객체가 언제 어떻게 remote로 바뀌는지까지 코드로 드러난다. 운영자는 여기서 remote metadata 정합성, file cache hit ratio, prefetch 비용을 같이 봐야 한다.


4. 이 릴리스가 특히 중요한 팀

1) 동적 필드가 많은 로그/이벤트 검색 클러스터

필드 종류가 많고 mapping update 때문에 cluster-manager가 자주 흔들린다면, 3.7의 dynamic_properties는 단순 편의 기능이 아니라 제어면 부하를 낮추는 아키텍처 변경이다.

2) hot/warm/remote segment store를 실제 운영하려는 팀

warm tier를 단순 보존 계층이 아니라 조회 가능한 계층으로 쓰려면, prefetch/metric/file cache/JVM 임계치를 함께 봐야 한다. 3.7은 이 현실을 API와 설정으로 드러낸다.

3) 검색 엔진을 columnar 또는 다중 포맷 인덱싱으로 확장하려는 팀

Parquet 경로는 아직 실험적이지만, OpenSearch가 Lucene-only 설계에서 한 발 더 나갔다는 신호다. 특히 cold analytics와 search를 한 엔진으로 다루려는 팀에게는 장기적으로 의미가 크다.


5. 도입 전 체크리스트

점검 영역먼저 확인할 것기대 신호문제 시 대응
dynamic_propertiessuffix/pattern 설계, field churn, mapping update 빈도mapping update round-trip 감소, query 시 type resolution 정상패턴을 더 좁히고 dynamic_templates와 역할 분리
쿼리/운영 도구field caps, dashboard, schema export가 패턴 필드를 어떻게 보는지필드가 낱개 매핑 없이도 필요한 질의가 동작운영 도구에 field catalog 보완 로직 추가
Parquet 포맷plugin/allocator/thread pool, checksum 비용, remote synccanary index에서 ingest/search/flush 경로 정상Lucene-only 경로 유지, merge flag 미사용
pluggable mergeopensearch.pluggable.dataformat.merge.enabled 실험 플래그켠 상태/끈 상태의 성능 차이가 설명 가능기본 OFF 유지, 포맷별 완성도 확인 전 확대 금지
warm tier APIsingle-index migration, rollback, status pollingGET /{index}/_tier/_tier/all이 shard 상태를 설명migration 자동화 전에 운영자 승인 단계 유지
warm 노드 자원file cache active %, JVM %, remote store latencythreshold 미만에서 안정적으로 이동동시 migration 수 축소, file cache/JVM 증설
prefetch/metricstored fields/doc values prefetch 성공률, slow log 노출warm read latency tail이 완만read_ahead와 prefetch 설정 보수적으로 되돌림

6. 한 줄로 정리하면

OpenSearch 3.7은 “검색 기능이 늘었다”보다 검색 엔진을 어떤 저장·매핑·포맷 계약 위에 올릴 것인가를 다시 묻는 릴리스다.

  • dynamic_properties는 동적 필드 폭증을 cluster state를 덜 흔드는 방식으로 받아들이려는 변화다.
  • Parquet 경로는 OpenSearch가 Lucene 외 포맷을 진짜 엔진 책임 아래로 끌어들이려는 시도다.
  • WritableWarm tiering은 warm 계층을 “싼 보관소”가 아니라 API와 메트릭을 갖춘 조회 계층으로 취급하기 시작했다.

다만 셋 다 공통점이 있다. 좋은 방향이지만, 아직 운영 표준으로 삼기 전에 canary와 rollback을 반드시 설계해야 한다. 3.7은 기능 체크리스트보다 아키텍처 질문에 더 먼저 답해야 하는 릴리스다.

References

  • OpenSearch release feed — OpenSearch 3.7.0 published 2026-06-09: https://github.com/opensearch-project/OpenSearch/releases.atom
  • OpenSearch 3.7.0 release notes: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/release-notes/opensearch.release-notes-3.7.0.md
  • DynamicProperty.java — pattern-based mapping without index mapping updates: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/index/mapper/DynamicProperty.java
  • DynamicPropertyFieldTypeResolver.java — query-time field type resolution and 1024-entry LRU cache: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/index/mapper/DynamicPropertyFieldTypeResolver.java
  • DynamicMappingTests.javadynamicMappingsUpdate() stays null for dynamic_properties: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/test/java/org/opensearch/index/mapper/DynamicMappingTests.java
  • DataFormatAwareEngine.java — pluggable engine lifecycle and merge feature flag: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/index/engine/DataFormatAwareEngine.java
  • ParquetDataFormatPlugin.java — Parquet format/plugin/store-strategy registration: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/sandbox/plugins/parquet-data-format/src/main/java/org/opensearch/parquet/ParquetDataFormatPlugin.java
  • DataFormatAwareStoreDirectory.java — format-specific file layout and checksum strategy: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/index/store/DataFormatAwareStoreDirectory.java
  • RestBaseTierAction.java, RestGetTieringStatusAction.java, RestListTieringStatusAction.java — tiering API surface: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/storage/action/tiering/RestBaseTierAction.java
  • HotToWarmTieringService.java and TieringUtils.java — migration state changes, concurrency and safety thresholds: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/storage/tiering/HotToWarmTieringService.java
  • TieredStoragePrefetchSettings.java and TieredStorageQueryMetricService.java — read-ahead/prefetch defaults and per-query metrics: https://github.com/opensearch-project/OpenSearch/blob/3.7.0/server/src/main/java/org/opensearch/storage/prefetch/TieredStoragePrefetchSettings.java