<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[인덱스 데이터 - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[인덱스 데이터 - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/kr/search-labs/blog/category/index-data</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/index-data</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/index-data.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 00:42:02 GMT</lastBuildDate>
  <item>
    <title><![CDATA[TSDS와 ILM의 만남: 늦게 도착하는 데이터를 거부하지 않는 시계열 데이터 스트림 설계]]></title>
    <description><![CDATA[TSDS 시간 제한이 ILM 단계와 상호 작용하는 방법과 늦게 도착하는 메트릭을 처리할 수 있는 정책 설계 방법]]></description>
    <content:encoded><![CDATA[<p>최근 한 고객의 메트릭 클러스터를 '모든 데이터를 hot 티어에 두는' 구조에서 hot/cold/frozen 아키텍처로 전환했습니다. 이미 수십 번은 해 본 익숙한 작업이었습니다. 그런데 몇 분 만에 Logstash에서 데이터가 더 이상 진행되지 않았습니다.</p><p>Elasticsearch는 늦게 도착한 메트릭을 거부하고 있었습니다. 이로 인해 파이프라인이 밀리기 시작했고, 더 많은 데이터가 늦게 도착하면서 거부가 더욱 늘어나게 되었습니다. 결국 파이프라인은 완전히 멈춰 섰습니다.</p><p>스냅샷에서 복원하고, 데이터를 다시 인덱싱하고, 수집 파이프라인을 다시 설계하여 복구해야 했습니다.</p><p>근본 원인은 인덱스 수명 주기 관리(ILM) 자체가 아니라, 시계열 데이터 스트림(TSDS)과 이를 통해 시간 제한 백킹(backing) 인덱스를 강제하는 방식에 있었습니다.</p><p>TSDS는 메트릭의 저장 공간 요구 사항을 40–70% 줄일 수 있지만, TSDS를 효율적으로 만드는 아키텍처 변경으로 인해 시간이 지남에 따라 인덱스의 동작 방식도 달라집니다. 이러한 변화는 ILM 정책을 설계하거나 수집 파이프라인에서 데이터가 늦게 도착할 수 있는 상황에서 중요합니다.</p><h2>요약</h2><p>TSDS를 사용하는 경우:</p><ul><li><p>백킹 인덱스는 특정 시간 범위에 해당하는 문서만 허용합니다.</p></li><li><p>인덱스가 cold 또는 frozen으로 이동한 뒤 데이터가 늦게 도착하면, Elasticsearch는 해당 문서를 수용하지 않거나(설정된 경우) failure store로 라우팅합니다.</p></li></ul><p>설계 규칙:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<h2>시계열 데이터 스트림이란 무엇인가요?</h2><p><em>시계열 데이터 스트림</em>(TSDS)은 메트릭 데이터에 최적화된 특수한 데이터 스트림입니다. 데이터는 관련 문서가 동일한 샤드 내에 위치하도록 라우팅되어 쿼리 및 검색에 최적화됩니다. Elasticsearch가 이를 수행하는 방법은 다음과 같습니다.</p><p>각 문서에는 다음이 포함됩니다.</p><ul><li><p>타임스탬프.</p></li><li><p>시계열을 식별하는 dimension 필드.</p></li><li><p>측정값을 나타내는 metric 필드.</p></li></ul><p>예를 들면 다음과 같습니다.</p><ul><li><p>호스트당 CPU 사용량.</p></li><li><p>서비스별 요청 지연 시간.</p></li><li><p>센서별 온도 측정값.</p></li></ul><p><em>dimensions</em>는 측정하고자 하는 대상을 식별하는 반면, <em>metrics</em>는 시간에 따라 변하는 값을 나타냅니다.</p><h3>dimensions</h3><p>dimensions는 측정되는 엔티티를 설명합니다.</p><p>예:</p>host.name
service.name
container.id<p>다음과 같이 매핑에서 정의합니다.</p>time_series_dimension: true<h3>metrics</h3><p>metrics는 숫자 값을 나타내며 다음을 사용하여 정의됩니다.</p>time_series_metric<p>일반적인 metric 유형:</p><ul><li><p>gauge: 오르내리는 값.</p></li><li><p>counter: 재설정될 때까지 증가하는 값.</p></li></ul><p>Elastic Agent는 주로 메트릭과 로그 데이터를 수집하므로, TSDS 인덱스를 직접 활성화하지 않은 경우에도 클러스터에 해당 인덱스가 여전히 존재할 수 있습니다.</p><h3>_tsid 필드</h3><p>Elasticsearch는 내부적으로 dimension 필드로부터 <code>_tsid</code> 값을 생성합니다. 이를 통해 동일한 dimensions를 가진 문서는 동일한 샤드로 라우팅되어 다음을 개선합니다.</p><ul><li><p>압축.</p></li><li><p>쿼리 로컬리티.</p></li><li><p>집계 성능.</p></li></ul><h2>주요 차이점: 시간 제한 백킹 인덱스</h2><p>기존 데이터 스트림은 항상 <em>쓰기 인덱스</em>라고 하는 가장 최근의 백킹 인덱스에 기록되지만, TSDS는 다르게 동작합니다.</p><p>각 TSDS 백킹 인덱스에는 정의된 시간 구간이 있으며 해당 구간에 해당하는 <code>@timestamp</code> 값을 가진 문서만 허용합니다.</p>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }<p>문서가 인덱싱되면 Elasticsearch는 해당 타임스탬프를 담당하는 백킹 인덱스로 라우팅하는데, 이는 기존의 인덱스와 달리 TSDS가 여러 백킹 인덱스에 동시에 쓰기를 수행할 수 있음을 의미합니다.</p><p>그 예는 다음과 같습니다.</p><ul><li><p>실시간 데이터 → 최신 인덱스.</p></li><li><p>지연된 데이터 → 해당 기간을 포함하는 이전 인덱스.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b001853af30d5f8/6a17dc2cfaa9137a7d93c751/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" alt="최신 문서는 이전 인덱스로 라우팅되고, 현재 문서는 최신 인덱스로 라우팅되는 과정을 보여주는 타임라인입니다." /><h2>늦게 도착하는 데이터를 위한 설계</h2><p>실제 수집 파이프라인은 메트릭을 제시간에 완벽하게 전달하는 경우가 거의 없습니다. 메트릭은 네트워크 장애, 백로그, 배치 수집, 엣지 디바이스의 연결 끊김 등으로 인해 지연될 수 있으며, 다시 연결되면 밀린 데이터를 시작합니다.</p><p>기존 인덱스는 이러한 지연을 조용히 흡수하지만, TSDS는 그렇지 않습니다.</p><p>문서의 타임스탬프가 쓰기 가능한 백킹 인덱스의 범위를 벗어나면 Elasticsearch가 이를 거부하므로, ILM 정책은 늦게 도착하는 데이터를 반드시 고려해야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e8eae1b2ddad142/6a17dc2e1d1b8335a793e35c/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" alt="인덱스 수명 주기 타임라인" /><h2>핵심 제약 조건</h2><p>백킹 인덱스는 지연 데이터를 수용할 수 있을 만큼 충분히 오랫동안 쓰기 가능 상태를 유지해야 합니다.</p><p>실제로는 다음과 같다.</p>time_until_readonly &gt; maximum_expected_lateness<p>ILM은 롤오버 시점부터 인덱스 나이를 측정하므로, 운영 규칙은 다음과 같습니다.</p>warm_or_cold_min_age &gt; rollover_max_age + maximum_expected_lateness<p></p><p>예를 들어, 메트릭이 최대 6시간 늦게 도착할 수 있는 경우 인덱스는 롤오버 후 최소 6시간 동안 쓰기 가능한 상태로 유지되어야 합니다.</p><p></p><p>이러한 제약 조건을 고려하지 못한 것이 바로 앞서 설명한 데이터 수집 실패의 원인이었습니다. 늦게 도착한 데이터는 이전 인덱스로 전달됐는데, 해당 인덱스는 이미 cold 티어에 있어서 쓰기가 차단된 상태였습니다.</p><p></p><h2>거부된 문서를 처리하는 방법</h2><p>TSDS가 문서를 거부하면 Elasticsearch는 오류를 반환하는데, 이는 타임스탬프가 쓰기 가능한 인덱스 범위 내에 속하지 않음을 의미합니다. 데이터 수집 파이프라인이 이 오류를 어떻게 처리하느냐에 따라 데이터 손실이 발생할 수도 있고, 수집이 중단될 수도 있습니다.</p><p>거부된 문서를 처리하는 주요 메커니즘은 failure store입니다.</p><h3>failure store(Elasticsearch 9.1 이상에서 권장)</h3><p>Elasticsearch 9.1에서는 failure store를 도입하여 거부된 문서를 자동으로 수집합니다. Elasticsearch는 클라이언트에 오류를 반환하는 대신 실패한 문서를 데이터 스트림 내부의 전용 failure 인덱스에 기록합니다.</p><p>다음을 사용하여 오류를 검사할 수 있습니다:</p>GET metrics-myapp::failures/_search<p>failure store를 사용하면 수집 파이프라인이 거부 오류로 인해 막히는 것을 방지하면서, 실패한 데이터를 분석 또는 <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">재인덱싱</a>을 위해 보존할 수 있습니다.</p><h2>거부 관련 문제 모니터링</h2><p>늦게 도착하는 문제는 일반적으로 수집 이상 징후로 먼저 나타납니다. 다음과 같은 현상으로 먼저 감지될 수 있습니다.</p><ul><li><p>인덱싱 속도의 급격한 감소.</p></li><li><p>거부된 문서 수의 급증.</p></li><li><p>failure store 항목 수 증가.</p></li><li><p>파이프라인 입력과 출력 개수 간의 불일치.</p></li></ul><p>이러한 신호를 기반으로 한 알림을 통해 운영자는 파이프라인이 중단되기 전에 문제를 감지할 수 있습니다. 워크플로우, 머신 러닝 작업 등 다양한 메커니즘을 활용해 탐지 및 알림을 자동화할 수 있습니다.</p><h2>TSDS + ILM 마이그레이션 체크리스트</h2><p>TSDS로 메트릭 클러스터를 마이그레이션하거나, ILM 계층화를 도입하거나, 메트릭이 기본적으로 TSDS인 Elasticsearch 버전으로 업그레이드하는 경우, 먼저 다음 항목을 검토하세요.</p><h3><strong>1. 수집 지연 시간 측정</strong></h3><p>ILM 정책을 변경하기 전에 다음 사항을 확인하세요.</p><ul><li><p>정상적인 수집 지연 시간.</p></li><li><p>인시던트 상황에서의 최대 지연.</p></li><li><p>배치 파이프라인으로 인한 지연.</p></li></ul><p>ILM 설계는 현실적으로 발생할 수 있는 최대 지연을 반드시 고려해야 합니다.</p><h3><strong>2. 인덱스 시간 구간 확인</strong></h3><p>TSDS 백킹 인덱스를 확인합니다.</p>GET _data_stream/&lt;your-stream&gt;<p>다음을 확인하세요.</p><ul><li><p><code>time_series.start_time</code></p></li><li><p><code>time_series.end_time</code></p></li></ul><p>이러한 경계는 어떤 인덱스가 문서를 수용할 수 있는지를 결정합니다. 이러한 시간 구간을 이해하면 데이터가 어느 정도까지 지연될 수 있는지(거부되기 전까지)를 판단하는 데 도움이 됩니다.</p><h3><strong>3. 지연 데이터를 위한 hot 티어 크기 조정</strong></h3><p>백킹 인덱스는 지연 데이터를 처리할 수 있도록 충분한 기간 동안 쓰기 가능 상태를 유지해야 한다.</p><p>운영 규칙:</p><ul><li><p><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></p></li></ul><p>메트릭이 6시간 늦게 도착할 수 있는 경우, 인덱스는 최소 6시간 동안 쓰기 가능 상태를 유지해야 한다는 점을 잊지 마세요.</p><h3><strong>4. 거부된 문서 처리 방식 결정</strong></h3><p>TSDS를 활성화하기 전에 전략을 선택하세요.</p><ul><li><p>failure store(Elasticsearch 9.1 이상에서 권장)</p></li><li><p>Logstash dead letter queue.</p></li><li><p>지연 데이터 처리를 위한 대체 인덱스.</p></li><li><p>제한적인 데이터 손실 허용.</p></li></ul><h3><strong>5. 데이터 수집 상태 모니터링</strong></h3><p>다음 항목에 대한 알림을 추가하세요.</p><ul><li><p>인덱싱 속도 하락.</p></li><li><p>거부된 문서.</p></li><li><p>failure store 증가</p></li><li><p>파이프라인 입력/출력 불일치.</p></li></ul><p>늦게 도착하는 데이터 문제는 종종 수집 이상 징후로 먼저 나타납니다.</p><h2>요약</h2><p>시계열 데이터 스트림은 메트릭 워크로드의 스토리지와 성능을 크게 개선하지만, 중요한 아키텍처 변경을 수반합니다. 백킹 인덱스는 시간 기반으로 제한되며, 이는 ILM의 동작 방식에 영향을 미칩니다.</p><p>TSDS를 사용하는 경우:</p><ul><li><p>인덱스는 지연 데이터를 수용할 수 있을 만큼 충분히 오랫동안 쓰기 가능 상태를 유지해야 합니다.</p></li><li><p>수집 파이프라인은 거부된 문서를 안정적으로 처리해야 합니다.</p></li></ul><p>기억해야 할 핵심 규칙은 다음과 같습니다:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<p>이 제약 조건을 중심으로 ILM 정책을 설계하면 TSDS는 메트릭 워크로드에 매우 효과적으로 작동합니다.</p><p>하지만 이를 무시하면 수집 파이프라인이 이러한 시간 경계를 직접 겪으며 문제를 발견하게 될 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcacf154aeacb29d8/6a17dc30dbb4ffc7ddfb557f/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[코드로서의 Elastic Open Web Crawler]]></title>
    <description><![CDATA[변경 사항을 리포지토리에 푸시할 때마다 배포된 크롤러 인스턴스에 변경 사항이 자동으로 적용되도록 GitHub Actions를 사용하여 Elastic Open Crawler 구성을 관리하는 방법에 대해 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://github.com/elastic/crawler">Elastic Open Web Crawler와</a> CLI 기반 아키텍처를 사용하면 이제 버전이 관리되는 크롤러 구성과 로컬 테스트가 포함된 CI/CD 파이프라인을 매우 간단하게 구축할 수 있습니다.</p><p>기존에는 크롤러 관리가 수작업으로 이루어졌고 오류가 발생하기 쉬운 프로세스였습니다. UI에서 직접 구성을 편집하고 크롤링 구성 복제, 롤백, 버전 관리 등으로 어려움을 겪어야 했습니다. 크롤러 구성을 코드로 취급하면 소프트웨어 개발에서 기대하는 반복성, 추적성 및 자동화와 같은 이점을 제공함으로써 이 문제를 해결할 수 있습니다.</p><p>이 워크플로우를 사용하면 Elastic Web Crawler나 App Search Crawler와 같은 이전 Elastic Crawler에서는 훨씬 더 까다로웠던 롤백, 백업, 마이그레이션 작업을 위해 CI/CD 파이프라인에 Open Web Crawler를 더 쉽게 도입할 수 있습니다.</p><p>이 글에서는 그 방법을 알아보겠습니다:</p><ul><li><p>GitHub를 사용하여 크롤링 구성 관리하기</p></li><li><p>배포하기 전에 파이프라인을 테스트할 수 있는 로컬 설정이 있습니다.</p></li><li><p>메인 브랜치에 변경 사항을 푸시할 때마다 새로운 설정으로 웹 크롤러를 실행하도록 프로덕션 설정을 만듭니다.</p></li></ul><p>프로젝트 리포지토리는 <a href="https://github.com/llermaly/elastic-open-crawler-as-code"><em><strong>여기에서</strong></em></a>찾을 수 있습니다<em><strong>. </strong></em><em>이 글을 쓰는 현재, 저는 Elasticsearch 9.1.3과 Open Web Crawler 0.4.2를 사용하고 있습니다.</em></p><h2>필수 구성 요소</h2><ul><li><p>Docker 데스크톱</p></li><li><p>Elasticsearch 인스턴스</p></li><li><p>SSH 액세스 권한이 있는 가상 머신(예: AWS EC2) 및 Docker가 설치된 가상 머신</p></li></ul><h2>단계</h2><ol><li><p>폴더 구조</p></li><li><p>크롤러 구성</p></li><li><p>Docker-작성 파일(로컬 환경)</p></li><li><p>깃허브 액션</p></li><li><p>로컬 테스트</p></li><li><p>프로덕션에 배포</p></li><li><p>변경 및 다시 배포</p></li></ol><h2>폴더 구조</h2><p>이 프로젝트의 파일 구조는 다음과 같습니다:</p>├── docker-compose.yml # Local elasticsearch + crawler
├── config/crawler-config.yml # Crawler config
├── .github/workflows/deploy.yml # GH Action to deploy changes
├── local.sh # Script to run our local crawler<h2>크롤러 구성</h2><p><code>crawler-config.yml,</code> 아래에 다음을 입력합니다:</p>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3<p>제품 모의 <a href="https://web-scraping.dev/products">사이트인 https://web-scraping.dev/products</a> 에서 크롤링됩니다. 처음 세 개의 제품 페이지만 크롤링합니다. <code>max_crawl_depth</code> 설정은 크롤러가 <code>seed_urls</code> 으로 정의된 페이지보다 더 많은 페이지를 발견하지 못하도록 하여 그 안의 링크를 열지 않도록 합니다.</p><p>Elasticsearch <code>host</code> 및 <code>api_key</code> 는 스크립트를 실행하는 환경에 따라 동적으로 채워집니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d37b966aafbd3c1/6a17ef9842022946e929f6b9/f9831034e1c4ccb554d37bdd188f2824338355a0-890x624.png" alt="웹 스크래핑 테스트용 모의 사이트인 web-scraping.dev 도메인의 &quot;상자 초콜릿 캔디&quot; 제품 페이지입니다. 이 페이지에는 제품 제목, 이미지, 설명, 가격 및 구매 버튼에 대한 HTML 요소가 표시됩니다." /><h2>Docker-작성 파일(로컬 환경)</h2><p>로컬 <code>docker-compose.yml,</code> 의 경우, 프로덕션에 배포하기 <em><strong>전에</strong></em> 크롤링 결과를 쉽게 시각화할 수 있도록 크롤러와 단일 Elasticsearch 클러스터 + Kibana를 배포합니다.</p>services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.1.3
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    networks: [esnet]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9200"]
      interval: 5s
      timeout: 5s
      retries: 10

  kibana:
    image: docker.elastic.co/kibana/kibana:9.1.3
    environment:
      - ELASTICSEARCH_HOSTS=http://es01:9200
    ports:
      - "5601:5601"
    networks: [esnet]
    depends_on: [es01]

  crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    stdin_open: true
    tty: true

networks:
  esnet:
    driver: bridge<p>크롤러가 Elasticsearch를 실행할 준비가 될 때까지 기다리는 방식에 주목하세요.</p><h2>깃허브 액션</h2><p>이제 새 설정을 복사하고 메인으로 푸시할 때마다 가상 머신에서 크롤러를 실행하는 GitHub 액션을 만들어야 합니다. 이렇게 하면 가상 머신에 수동으로 들어가서 파일을 업데이트하고 크롤러를 실행할 필요 없이 항상 최신 구성이 배포되어 있습니다. 가상 머신 공급자로 AWS EC2를 사용하겠습니다.</p><p>첫 번째 단계는 호스트(<code>VM_HOST</code>), 머신 사용자(<code>VM_USER</code>), SSH RSA 키(<code>VM_KEY</code>), Elasticsearch 호스트(<code>ES_HOST</code>), Elasticsearch API 키(<code>ES_API_KEY</code>)를 GitHub Action 시크릿에 추가하는 것입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5a0fcfff9b7f997/6a17ef9a6df731d7d40a0fdf/e1075bc54151b4b94eac2a6bd2682e9997e6c709-1106x707.png" alt="&quot;작업, 비밀 및 변수&quot; 구성 페이지로, 웹 기반 인터페이스 내에 VM_HOST, VM_KEY, VM_USER와 같은 리포지토리 비밀을 표시합니다." /><p>이렇게 하면 액션이 서버에 액세스하여 새 파일을 복사하고 크롤링을 실행할 수 있습니다.</p><p>이제 <code>.github/workflows/deploy.yml</code> 파일을 만들어 보겠습니다:</p>name: Deploy

on:
  push:
    branches: [main]

jobs:
  Deploy:
    name: Deploy to EC2
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Deploy crawler
        env:
          HOSTNAME: ${{ secrets.VM_HOST }}
          USER_NAME: ${{ secrets.VM_USER }}
          PRIVATE_KEY: ${{ secrets.VM_KEY }}
          ES_HOST: ${{ secrets.ES_HOST }}
          ES_API_KEY: ${{ secrets.ES_API_KEY }}
        run: |
          # Save private key
          echo "$PRIVATE_KEY" &gt; private_key
          chmod 600 private_key

          # Generate final config locally
          envsubst &lt; config/crawler-config.yml &gt; config/crawl-config-final.yml

          # Copy the config folder to VM
          scp -o StrictHostKeyChecking=no -i private_key -r config ${USER_NAME}@${HOSTNAME}:~/config

          # SSH into VM and run crawler
          ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOSTNAME} &lt;&lt; EOF
            docker run --rm \
              -v ~/config:/config \
              docker.elastic.co/integrations/crawler:latest jruby \
              bin/crawler crawl /config/crawl-config-final.yml
          EOF<p>이 작업은 크롤러 구성 파일에 변경 사항을 푸시할 때마다 다음 단계를 실행합니다:</p><ol><li><p>yml 구성에서 Elasticsearch 호스트 및 API 키 채우기</p></li><li><p>구성 폴더를 VM에 복사합니다.</p></li><li><p>SSH를 통해 VM에 연결</p></li><li><p>리포지토리에서 방금 복사한 구성으로 크롤링을 실행합니다.</p></li></ol><h2>로컬 테스트</h2><p>로컬에서 크롤러를 테스트하기 위해, Docker의 로컬 호스트에 Elasticsearch 호스트를 채우고 크롤링을 시작하는 bash 스크립트를 만들었습니다. <code>./local.sh</code> 을 실행하여 실행할 수 있습니다.</p>#!/bin/bash

# Exit on any error
set -e

# Load environment variables
export ES_HOST="http://es01:9200"

# Generate final crawler config
envsubst &lt; ./config/crawler-config.yml &gt; ./config/crawl-config-final.yml

# Bring everything up
docker compose up --build<p>Kibana 개발자 도구를 살펴보고<code> web-crawler-index</code> 이 올바르게 채워졌는지 확인해 보겠습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt989660368fe14db8/6a17ef9b9da390c79ce46562/18551635e8265866e389a9632c4e4540958e4468-990x723.png" alt="웹 크롤러 인덱스가 올바르게 설정되었는지 확인하기 위한 Kibana 개발자 도구 코드입니다." /><h2>프로덕션에 배포</h2><p>이제 가상 머신에 크롤러를 배포하고 서버리스 Elasticsearch 인스턴스로 로그 전송을 시작하는 메인 브랜치로 푸시할 준비가 되었습니다.</p>git add .
git commit -m "First commit"
git push<p>그러면 가상 머신 내에서 배포 스크립트를 실행하고 크롤링을 시작하는 GitHub 액션이 트리거됩니다.</p><p>GitHub 리포지토리로 이동하여 "작업" 탭을 방문하면 작업이 실행되었는지 확인할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt986f1a4e4f3288d2/6a17ef9c7f6f1584fdc09c10/67ba3a7164d7a8049fe5661264820826cb18ed64-667x325.png" alt="작업은 GitHub 리포지토리의 EC2 탭에 배포됩니다." /><h2>변경 및 다시 배포</h2><p>눈에 띄는 점은 각 제품의 <code>price</code> 이 문서 본문 필드의 일부라는 점입니다. 가격을 별도의 필드에 저장하여 필터를 실행할 수 있도록 하는 것이 가장 이상적입니다.</p><p><code>crawler.yml</code> 파일에 이 변경 사항을 추가하여 <a href="https://github.com/elastic/crawler/blob/main/docs/features/EXTRACTION_RULES.md">추출 규칙을</a> 사용하여 <code>product-price</code> CSS 클래스에서 가격을 추출해 보겠습니다:</p>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
  # Index ingest pipeline to process documents before indexing          
  pipeline_enabled: true
  pipeline: pricing-pipeline

domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3
    extraction_rulesets:
      - url_filters:
          - type: ends
            pattern: /product/*
        rules:
          - action: extract
            field_name: price
            selector: .product-price
            join_as: string
            source: html<p>또한 가격에는 달러 기호(<code>$</code>)가 포함되어 있으며, 범위 쿼리를 실행하려면 이 기호를 제거해야 합니다. 이를 위해 수집 파이프라인을 사용할 수 있습니다. 위의 새 크롤러 구성 파일에서 이를 참조하고 있습니다:</p>PUT _ingest/pipeline/pricing-pipeline
{
  "processors": [
    {
      "script": {
        "source": """
                ctx['price'] = ctx['price'].replace("$","")
            """
      }
    }
  ]
}<p>프로덕션 Elasticsearch 클러스터에서 해당 명령을 실행할 수 있습니다. 개발의 경우, 임시적이므로 다음 서비스를 추가하여 <code>docker-compose.yml</code> 파일에 파이프라인 생성 부분을 만들 수 있습니다. 또한 크롤러 서비스에 <code>depends_on</code> 을 추가하여 파이프라인이 성공적으로 생성된 후에 시작되도록 했습니다.</p> crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    depends_on:
      pipeline-init:
        condition: service_completed_successfully
    stdin_open: true
    tty: true  


  pipeline-init:
    image: curlimages/curl:latest
    depends_on:
      es01:
        condition: service_healthy
    networks: [esnet]
    entrypoint: &gt;
        sh -c "
        echo 'Creating ingest pipeline...';
        curl -s -X PUT http://es01:9200/_ingest/pipeline/pricing-pipeline \\
          -H 'Content-Type: application/json' \\
          -d '{\"processors\":[{\"script\":{\"source\":\"ctx.price = ctx.price.replace(\\\"$\\\", \\\"\\\")\"}}]}';
        echo 'Pipeline created!';
        "<p>이제 <code>`./local.sh`</code> 을 실행하여 로컬에서 변경 사항을 확인해 보겠습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f390aedf67cb4fe/6a17ef9eaf47b62bd2cde05b/dc1801599344a9f69f072b07ff828c4ba3815d7b-738x473.png" alt="./local.sh`를 실행하여 로컬에서 가격 변동을 확인합니다." /><p>훌륭합니다! 이제 변경 사항을 적용해 보겠습니다:</p>git add crawler-config.yml
git commit -m "added price CSS selector"
git push<p>모든 것이 제대로 작동하는지 확인하려면 프로덕션 Kibana를 확인하면 변경 사항이 반영되어 달러 기호가 없는 새 필드로 가격이 표시되어야 합니다.</p><h2>결론</h2><p>Elastic Open Web Crawler를 사용하면 크롤러를 코드로 관리할 수 있으므로 개발부터 배포까지 전체 파이프라인을 자동화하고 임시 로컬 환경을 추가하고 크롤링된 데이터에 대해 프로그래밍 방식으로 테스트하는 등 몇 가지 예를 들 수 있습니다.</p><p>공식 리포지토리를 복제하고 이 워크플로우를 사용하여 자체 데이터 색인화를 시작할 수 있습니다. <a href="https://www.elastic.co/search-labs/blog/semantic-search-open-crawler">이 문서에서</a> 크롤러가 생성한 인덱스에 대해 시맨틱 검색을 실행하는 방법을 알아볼 수도 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-open-crawler-config-as-code</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-open-crawler-config-as-code</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <category><![CDATA[운영]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00e7c95012dc38cc/6a17efa0fbc5f80dad491b93/0ac41f55c85ad3f647cb0e0d750ed80bacd397f3-1036x581.png" length="0" type="image/png"/>
    <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 인덱스의 필드를 표시하는 방법]]></title>
    <description><![CDATA[맵핑 및 _검색 API, 하위 필드, 합성 _소스 및 런타임 필드를 사용하여 Elasticsearch 인덱스의 필드를 표시하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 Elasticsearch 인덱스의 필드를 표시하는 방법에 대해 설명합니다. 이는 데이터 구조를 이해하고, 특정 필드를 식별하고, 문제를 해결하는 데 유용할 수 있습니다. 다음 주제를 다룰 예정입니다:</p><ol><li><p><code>_mapping</code> API를 사용하여 필드 정보 검색하기</p></li><li><p><code>_search</code> API를 사용하여 필드 값 표시</p></li><li><p>하위 필드 표시</p></li><li><p>Synthetic _source</p></li><li><p>런타임 필드</p></li></ol><h2>1. 맵핑 API를 사용하여 필드 정보 검색하기</h2><p><code>_mapping</code> API를 사용하면 인덱스 또는 여러 인덱스에 대한 매핑 정의를 검색할 수 있습니다. 여기에는 필드, 데이터 유형 및 기타 속성에 대한 정보가 포함됩니다. 특정 인덱스에 대한 매핑을 검색하려면 다음 요청을 사용하세요:</p>GET /&lt;index_name&gt;/_mapping<p>예를 들어 <code>my_index</code> 이라는 인덱스가 있는 경우 다음 요청으로 해당 인덱스의 매핑을 검색할 수 있습니다:</p>GET /my_index/_mapping<p>응답에는 필드 및 해당 속성에 대한 정보가 포함된 인덱스에 대한 매핑 정의가 포함됩니다.</p><p>특정 필드에 대한 매핑을 검색할 수도 있습니다. 매핑이 상당히 크고 특정 필드에만 집중하려는 경우 유용할 수 있습니다. 특정 필드의 매핑을 검색하려면 다음 요청을 사용하세요:</p>GET /my_index/_mapping/field/my_field<p>다음 요청에서와 같이 쉼표로 이름을 구분하여 여러 필드의 매핑을 검색할 수도 있습니다:</p>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3<h2>2. search API를 사용하여 필드 값 표시하기</h2><p>Elasticsearch 인덱스의 필드 값을 표시하려면 <code>_search</code> API를 사용하면 됩니다. <code>_search</code> API는 반환되는 필드를 제어할 수 있는 다양한 방법을 제공하며, 두 가지 주요 방법은 다음과 같습니다:</p><ol><li><p><strong><code>_source</code></strong>: <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field"><code>_source</code></a> 필드에는 수집 파이프라인이나 전처리 단계에 의해 변경된 사항을 포함하여 색인된 그대로의 원본 JSON 문서 본문이 포함되어 있습니다. 소스 문서의 특정 필드를 표시하려면 아래에서 설명하는 대로 소스 필터링을 구현합니다.</p></li><li><p><strong><code>fields</code></strong>: <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields"><code>fields</code></a> 매개변수를 사용하면 색인 매핑을 기반으로 검색을 수행할 때 문서에서 특정 필드를 검색할 수 있습니다. <code>_source</code> 과 달리 <code>fields</code> 은 <code>_source</code> 을 참조하지 않고 저장된 필드, 문서 값 또는 런타임 필드의 값을 반환할 수도 있지만 문서 값이나 저장된 설정이 없는 표준 필드의 경우 <code>_source</code> 으로 되돌아갑니다. 이는 아래에서 살펴보겠지만 성능 등 많은 이점을 가져올 수 있습니다.</p></li></ol><h3>소스필드 사용</h3><p>기본적으로<code> _search</code> API는 색인된 원본 JSON 문서가 포함된 <code>_source</code> 필드를 반환합니다. 특정 필드를 표시하려면 검색 요청의 <code>_source </code>매개변수에 필터를 추가할 수 있으며, 이를 소스 필터링이라고 합니다.</p><p>다음은 <code>my_index</code> 인덱스에 있는 문서에 대한 <code>title </code>및 <code>author</code> 필드 값을 반환하는 검색 요청의 예입니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}<p>이 예제에서 <code>_source</code> 매개변수는 반환할 필드를 지정합니다.</p><p>더 많은 제어가 필요한 경우 <code>_source</code> 객체의 <code>includes</code> 및 <code>excludes </code>속성을 사용할 수 있습니다. 예를 들어 아래 쿼리는 최상위 수준 <code>title</code> 필드와 <code>author</code> 의 <code>author.description</code> 을 제외한 모든 하위 필드를 반환합니다.</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}<p>이 예제에서는 <code>author.* </code>패턴을 사용하여 <code>author </code>객체의 모든 직접 하위 필드를 검색합니다. 그런 다음 <code>author.description </code>을 명시적으로 제외하여 다른 작성자 필드만 반환되도록 합니다. 이 경우에도 여전히 소스 JSON을 로드하고 구문 분석해야 하므로 성능이 향상되지는 않지만 네트워크를 통해 전송되는 응답의 크기를 줄일 수 있다는 점에 유의하세요.</p><h3>필드 매개변수 사용</h3><p><code>fields</code> 매개변수를 사용하여 검색 응답에 반환되는 필드를 필터링할 수 있습니다. <code>_source</code> 대신 <code>fields</code> 을 사용하면 다음과 같은 여러 가지 이점이 있습니다:</p><ul><li><p><strong>성능 개선: </strong><code>fields </code>은 전체 <code>_source</code> 을 로드할 필요 없이 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">저장된 필드</a> 또는 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/doc-values">문서 값에서</a> 직접 값을 반환할 수 있으므로 응답 페이로드 크기가 더 작아집니다.</p></li><li><p><strong>형식화된 출력:</strong> 표준 필드의 경우<code> fields</code> 은 <code>_source</code> 으로 되돌아가 값을 가져올 수 있지만, 인덱스 매핑을 확인하여 형식이 지정된 날짜와 같은 출력의 형식을 적절히 지정하여 집계 및 정렬에 사용되는 것과 일관성을 유지합니다.</p></li><li><p><strong>런타임 필드에 대한 액세스:</strong> <code>fields</code> 은 원본 <code>_source</code> 에 없는 런타임 필드를 반환할 수 있습니다.</p></li><li><p>더 많은 혜택은 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#search-fields-param">여기에서</a> 확인할 수 있습니다.</p></li></ul><p>예를 들어 <code>my_index</code> 인덱스에서 <code>title</code> 및 <code>author</code> 필드만 반환하려면 다음 검색 요청을 사용할 수 있습니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}<p>위의 쿼리에서는 소스 문서를 반환하지 않도록 <code>_source </code>필드를 false로 설정했습니다. 이렇게 하면 응답의 페이로드 크기를 크게 최소화할 수 있지만 <code>title</code> 및 <code>author</code> 필드가 <code>keyword </code>필드 유형이고 기본적으로 <code>doc_values</code> 이 활성화되어 있기 때문에 작동한다는 점을 기억하세요. 필드에 <code>doc_values</code> 가 활성화되어 있지 않고 <code>_source</code> 가 false로 설정되어 있으면, Elasticsearch는 이를 검색할 방법이 없으며 응답에서 건너뛰게 됩니다.</p><p><code>fields</code> 응답은 값이 하나만 있는 경우에도 항상 각 필드에 대한 값 배열을 반환한다는 점에 유의하세요. 이는 Elasticsearch에 전용 배열 유형이 없고 모든 필드에 여러 개의 값이 있을 수 있기 때문입니다. Elasticsearch의 배열에 대한 자세한 내용을 보려면 <a href="http://elastic.co/docs/reference/elasticsearch/mapping-reference/array">여기를</a> 클릭하세요.</p><h3>필드를 검색하는 다른 방법</h3><p><code>_source</code> 또는 <code>fields</code> 을 사용하여 필드를 검색하는 것이 권장되는 방법이지만, 특정 사용 사례에 따라 다음과 같은 다양한 방법을 사용할 수 있습니다:</p><p><strong>문서 값 필드:</strong> <code>_source</code> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#docvalue-fields"><code>docvalue_fields</code></a>매개 변수를 사용하여 검색할 수 있습니다. 문서 값은 <code>_source</code> 과 동일한 필드 값을 저장하지만 정렬 및 집계에 최적화된 온디스크 데이터 구조로 저장합니다.</p><p><code>_source</code> 에 저장된 값과는 별개이므로 전체 <code>_source</code> 를 로드하지 않고도 특정 필드를 요청할 수 있습니다. 이 기능은 대규모 문서를 쿼리하지만 문서 값을 지원하는 작은 필드 몇 개만 필요한 경우에 유용합니다. <code>docvalue_fields </code>사용의 또 다른 사용 사례는 아래 예제에서 볼 수 있듯이 <code>date</code> 및 <code>numeric</code> 필드에 사용자 지정 서식을 사용하려는 경우입니다.</p><p><code>doc_values</code> 을 활성화한 필드 또는 <code>keyword</code>, <code>date</code>, 숫자 유형 및 <code>boolean</code> 과 같이 기본적으로 활성화된 필드 유형에 대해서만 작동하며, <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/text"><code>text</code></a> 또는 <a href="https://www.elastic.co/docs/reference/elasticsearch/plugins/mapper-annotated-text-usage"><code>annotated_text</code></a> 에는 작동하지 않습니다.</p><p>이 예에서는 <code>docvalue_fields</code> 매개변수를 사용하여 전체 <code>_source</code> 문서를 로드하지 않고 <code>title</code>, <code>author</code>, <code>published</code> 필드를 검색합니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}<p>이 쿼리가 실행되면 Elasticsearch는 각 문서에 대해 <code>_source </code>을 참조하는 대신 온디스크 컬럼형 저장소에서 직접 값을 가져옵니다. <code>published</code> 필드는 쿼리에 제공된 <code>format</code> 매개변수 덕분에 기본 형식이 아닌 <code>epoch_millis</code> 형식으로 반환됩니다.</p><p><strong>저장된 필드:</strong> 매핑에서 특정 필드를 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">저장된</a> 것으로 명시적으로 표시한 경우 <code>stored_fields</code> 매개변수를 사용하여 해당 필드를 필터링할 수 있습니다. 특정 필드에 대해서만 가벼운 응답을 원하거나 나중에 검색할 수 있도록 의도적으로 저장한 필드에 대해 이 기능을 사용하면 유용합니다. <code>_source</code> 과 별도로 저장되므로 이 방법은 <code>_source</code> 을 로드할 필요가 없는 경우에도 유용합니다.</p><p>이 옵션은 기본적으로 꺼져 있으며 일반적으로 권장되지 않는다는 점에 유의하세요. 원본 소스 문서의 특정 하위 집합을 반환하려면 대신 소스 필터링을 사용하세요.</p><p>아래 예제 쿼리에서는 <code>stored_fields</code> 매개 변수를 사용하여 "<code>store”: true</code>" 인덱스 매핑 구성이 있는 <code>summary</code> 필드를 검색합니다.</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}<p>이 쿼리가 실행되면 Elasticsearch는 이 필드가 <code>”store”: true</code> 로 표시되어 있는지 확인하며, 이 필드를 찾지 못하면 필드를 완전히 건너뜁니다.</p><h2>3. 하위 필드 표시</h2><p>인덱스에 하위 필드가 포함된 경우 점 표기법을 사용하여 <code>fields</code> 매개변수에서 필드 경로를 지정할 수 있습니다. 하위 필드는 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/nested">중첩된 필드 유형과</a> 다르다는 점에 유의하세요. 예를 들어 <code>address.city</code> 이라는 이름의 하위 필드가 있는 경우 다음과 같이 검색 응답에 포함할 수 있습니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}<p>이 예제에서는 검색 응답에 <code>title</code>, <code>author</code>, <code>address.city</code> 필드의 값이 포함됩니다.</p><h2>4. 합성 _소스</h2><p><code> _source</code> 사용 기능을 유지하면서 디스크 공간도 절약하려면 인덱스 매핑에 합성 <code>_source</code> 을 사용하는 옵션이 있습니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field#synthetic-source">합성 </a><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field#synthetic-source"><code>_source</code></a> 은 <code>_source</code> 이 비활성화되어 있는 경우에도 Elasticsearch가 저장된 필드 및 문서 값과 같은 기존 데이터로부터 <code>_source</code> 을 재구성할 수 있도록 하는 기능입니다. 이렇게 하면 재구성이 즉시 이루어지므로 쿼리 시 속도가 약간 느려지는 대신 저장 공간을 많이 절약할 수 있습니다. 인덱스 설정에서 아래 값을 사용하여 이 기능을 사용하도록 설정합니다:</p>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}<p><code>_search</code> API를 사용할 때 전체 문서 표시, 소스 필터링, <code>_source</code> 를 사용할 수 있을 것으로 기대하는 Kibana와 같은 다른 기능 및 도구와의 호환성, 전체 <code>_source</code> 문서를 저장할 필요가 없는 것 등이 합성 <code>_source </code>사용의 몇 가지 이점입니다.</p><h2>5. 런타임 필드</h2><p>런타임 <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/runtime-fields">필드를</a> 사용하면 쿼리 시 또는 런타임 블록 아래의 인덱스 매핑에서 스크립트 필드를 정의할 수 있습니다. 이러한 필드는 색인화되지 않으므로 런타임 필드를 추가해도 색인 크기가 증가하지는 않지만 <code>_source</code> 에 표시되지 않습니다. 매핑에 정의된 런타임 필드는 영구적이며 모든 쿼리에서 사용할 수 있는 반면, 쿼리 시점에 정의된 런타임 필드는 임시적이며 해당 검색 요청에서만 사용할 수 있습니다.</p><p>런타임 필드 사용의 주요 이점은 이미 수집한 후 문서에 필드를 추가할 수 있어 매핑 결정을 간소화할 수 있다는 점입니다. 런타임 필드는 문자열 서식 지정이나 점수 계산과 같이 원본 문서에는 없지만 스크립트를 사용하여 생성된 값으로 문서를 보강하는 데도 유용합니다.</p><p>또한 런타임 필드는 결과 집합의 모든 문서에 대해 스크립트를 실행해야 하므로 성능이 저하될 수 있다는 점도 유의할 필요가 있습니다. <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/retrieve-runtime-field">런타임 필드를 검색하려면</a> <code>_search</code> API에서 <code>fields</code> 매개 변수를 사용할 수도 있습니다.</p><h2>결론</h2><p>Elasticsearch 인덱스의 필드를 표시하는 방법은 인덱스 매핑 또는 <code>_source</code> 을 사용하여 단순히 값을 검색하는 것부터 <code>fields</code>, <code>docvalue_fields</code> 또는 제어 및 효율성을 높이기 위한 런타임 필드를 사용하는 고급 방법까지 다양합니다. 검색 환경을 최적화하려면 다양한 방법 간의 장단점을 이해하는 것이 중요합니다. 페이로드를 최적화하든, 문서를 보강하든, 저장 공간을 절약하기 위해 합성 <code>_source</code> 을 사용하든, Elasticsearch는 필요한 데이터를 필요한 방식으로 찾을 수 있는 여러 가지 도구와 기능을 제공합니다. 이러한 기법은 데이터 구조를 이해하고, 특정 필드를 식별하고, 문제를 해결하는 데 도움이 될 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <category><![CDATA[매핑]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd041e871a8935448/6a17de320b0bedf404dd34ab/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Logstash의 루비 스크립팅]]></title>
    <description><![CDATA[Logstash 파이프라인에서 고급 데이터 변환을 위한 Logstash Ruby 필터 플러그인에 대해 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Logstash는 여러 소스에서 데이터를 수집하고 변환하여 선택한 대상으로 전송하는 데이터 처리 파이프라인입니다. 필터 플러그인은 이 프로세스의 핵심으로, 데이터가 파이프라인을 통과할 때 특정 작업을 수행합니다.</p><p>Logstash에는 데이터 구문 분석, 보강, 수정과 같은 일반적인 작업을 위한 몇 가지 기본 제공 필터가 포함되어 있습니다. 그러나 때로는 이러한 표준 필터가 제공할 수 있는 범위를 넘어서는 사용자 지정 로직이 필요한 시나리오가 발생할 수 있습니다. 바로 이 부분에서 <a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">루비 필터 플러그인이</a> 등장합니다.</p><p><strong>Ruby 필터 플러그인을 사용하면 Logstash 파이프라인 내에서 직접 사용자 정의 Ruby 코드를 실행할 수 있습니다.</strong> 표준 필터로 충분하지 않은 경우, 루비 필터를 사용하면 복잡한 데이터 변환을 처리하고, 사용자 지정 비즈니스 로직을 구현하거나, 외부 시스템과 통합할 수 있습니다.</p><p>이 블로그에서는 기본 사용법부터 고급 사용법까지 루비 필터를 사용하는 방법을 살펴봅니다.</p><h2>루비 필터는 언제 사용해야 하나요?</h2><p>Elastic의 컨설팅 아키텍트로서, 요즘은 최신 데이터 처리 엔진이 아니지만 데이터 처리 파이프라인에 Logstash를 사용하는 고객들을 자주 보게 됩니다. 복잡한 데이터 조작이나 사용자 지정 로직을 처리할 때 표준 필터의 한계로 인해 어려움을 겪는 경우가 많습니다. 이러한 경우 루비 필터를 사용하면 이러한 문제를 극복하는 데 도움이 될 수 있습니다.</p><p>Ruby 필터는 표준 Logstash 필터로 특정 요구 사항을 충족할 수 없을 때 유용합니다. 다음은 몇 가지 일반적인 사용 사례입니다:</p><ul><li><p><strong>심층 중첩 데이터 조작</strong>: 복잡한 JSON 구조, 배열 내의 배열을 수정하거나 콘텐츠에 따라 데이터를 동적으로 재구성합니다.</p></li><li><p><strong>고급 문자열 처리</strong>: 비정형 텍스트에서 정형 데이터 구문 분석 및 추출</p></li><li><p><strong>복잡한 비즈니스 로직 구현하기</strong>: 조건부 논리, 루프 또는 복잡한 계산이 필요한 사용자 지정 변환을 만듭니다.</p></li></ul><h2>기본 사용법</h2><p>루비 필터의 작동 원리를 이해하기 위해 간단한 예제부터 살펴보겠습니다.</p><h3>루비 필터 구성하기</h3><p>Logstash 파이프라인을 생성할 때, 구성 파일을 <code>/etc/logstash/conf.d</code> 디렉터리에 배치해야 합니다. 또는 Logstash를 수동으로 부팅할 때 <code>-f</code> 옵션을 사용하여 구성 파일의 경로를 지정하면 파이프라인을 쉽게 실험할 수 있습니다.</p>$ ./bin/logstash -f /path/to/your_pipeline.conf<p>구성 파일의 확장자는 <code>.conf</code> 여야 합니다.</p><p>Ruby 필터를 사용하려면 Logstash 파이프라인 구성(*.conf) 파일의 필터 섹션에 <code>ruby</code> 필터를 정의하세요. 다음은 기본적인 예입니다:</p>filter {
  ruby {
    code =&gt; "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}<p>이 인라인 Ruby 필터는 Logstash 구성 내에서 Ruby 필터 인스턴스를 정의합니다. <code>code</code> 매개변수는 이 필터가 처리하는 각 이벤트에 대해 Logstash가 실행할 인라인 Ruby 스크립트를 제공합니다. 해당 스크립트 내부에는 이벤트 자체를 나타내는 <code>event</code> 변수가 있습니다. 이벤트 객체에는 Logstash로 전송된 원본 데이터와 Logstash의 필터 단계에서 생성된 모든 추가 필드가 포함되어 있습니다. <code>event.get()</code> 및 <code>event.set()</code> 과 같은 Logstash 이벤트 API를 통해 이러한 필드에 액세스할 수 있습니다. 이 예제 코드에서 <code>event.set('new_field', 'Hello from Ruby!')</code> 는 <code>new_field</code> 이라는 새 필드를 문자열 값 <code>Hello from Ruby!</code> 으로 설정합니다. 필요에 따라 이 <code>code</code> 블록에 다른 코드를 추가할 수 있습니다.</p><p>이 <code>event</code> 객체는 키-값 유형의 데이터 컨테이너로 작동하지만 일반적인 Ruby의 해시 객체가 아닙니다. 이벤트 API에 대해 자세히 알아보려면 <a href="https://www.elastic.co/docs/reference/logstash/event-api">이 공식 문서를</a> 확인하세요.</p><h3>루비 스크립트 외부화</h3><p>간단한 변환의 경우 인라인 루비 코드가 편리합니다. 그러나 복잡한 로직이나 재사용 가능한 함수의 경우 코드를 외부 Ruby 스크립트로 옮기는 것이 좋습니다. 이렇게 하면 유지보수성이 향상되고 Logstash 파이프라인 구성이 깔끔하게 유지됩니다.</p><p>먼저 루비 스크립트를 만들어 <code>my_ruby_script.rb</code> 로 저장합니다. 스크립트는 이벤트를 처리하는 <code>filter</code> 메서드를 정의해야 합니다. 이 함수는 처리 중인 현재 이벤트를 나타내는 이벤트 객체를 인수로 받습니다. <code>filter</code> 메서드는 전송할 이벤트 배열을 반환해야 합니다. 이벤트를 삭제하려면 빈 배열을 반환합니다.</p><p>예를 들어 다음 스크립트는 <code>message</code> 필드를 읽고 길이를 계산한 다음 그 결과를 <code>message_length</code> 이라는 새 필드에 저장합니다.</p>def register(params)
  # This method is called when the plugin is loaded.
  # You can use it to initialize any instance variables or perform setup tasks.
end

def filter(event)
  message = event.get('message')

  if message
    event.set('message_length', message.length)
  end

  return [event]
end<p>그런 다음 <code>path</code> 옵션을 사용하여 스크립트를 참조하도록 루비 필터 구성을 설정합니다. 이렇게 하면 Logstash가 외부 스크립트를 로드하고 실행하도록 지시합니다. 외부 스크립트를 사용할 때는 파일이 존재하고 올바른 권한이 있는지 확인하세요.</p>filter {
  ruby {
    path =&gt; "/path/to/my_ruby_script.rb"
  }
}<p>이제 각 이벤트는 <code>my_ruby_script.rb</code> 의 <code>filter</code> 메서드로 전달되어 처리됩니다.</p><p>이 접근 방식을 사용하면 복잡한 로직을 보다 효과적으로 관리할 수 있으므로 Ruby 코드를 더 쉽게 테스트, 디버그 및 재사용할 수 있습니다.</p><h2>고급 사용 방법</h2><p>이 섹션에서는 Logstash에서 Ruby 필터를 사용하는 몇 가지 고급 예제를 살펴보겠습니다. 이 예제에서는 Ruby를 사용하여 데이터 변환을 수행하고, 이벤트를 보강하고, 사용자 지정 로직을 구현하는 방법을 보여드립니다.</p><h3>중첩된 데이터 구조 조작하기</h3><p>Logstash 이벤트는 Logstash가 처리하는 핵심 데이터 구조입니다. 배열 및 해시 같은 중첩된 데이터 구조를 포함하여 다양한 필드를 포함할 수 있습니다. 루비 필터를 사용하면 이러한 중첩 구조를 쉽게 조작할 수 있습니다.</p><p>루비 필터는 해시 및 배열과 같은 중첩된 데이터 구조를 처리할 수 있으므로 이러한 구조 내에서 필드를 수정하거나 추가할 수 있습니다. 이는 JSON과 같은 복잡한 데이터 형식을 다룰 때 유용합니다.</p>input {
  generator {
    lines =&gt; [
      '{"nested": {"key1": "value1", "key2": "value2"}}'
    ]
    count =&gt; 1
    codec =&gt; "json"
    ecs_compatibility =&gt; "disabled"
  }
}

filter {
  ruby {
    code =&gt; "
      nested_data = event.get('nested')

      if nested_data.is_a?(Hash)
        nested_data['key3'] = 'value3'
        event.set('nested', nested_data)
      end
    "
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>이 예제에는 입력 데이터에 중첩된 JSON 객체가 포함되어 있습니다. 루비 필터는 새로운 키-값 쌍을 추가하여 중첩된 데이터를 수정합니다. 중첩된 데이터에 대한 이러한 유형의 조작은 표준 Logstash 필터로는 불가능하므로, 복잡한 데이터 구조에 편리한 옵션으로 Ruby 필터를 사용할 수 있습니다.</p><h3>단일 이벤트를 여러 이벤트로 분할</h3><p>루비 필터를 사용하여 단일 이벤트를 여러 개의 이벤트로 분할할 수도 있습니다. 여러 항목이 포함된 단일 이벤트가 있고 각 항목에 대해 별도의 이벤트를 만들려는 경우에 유용합니다.</p><p>Elasticsearch의 수집 파이프라인이나 Beats/Elastic Agent의 프로세서 모두 이벤트 분할을 지원하지 않습니다. 이것은 Logstash의 가장 강력한 사용 사례 중 하나입니다.</p><h4>분할 필터 사용</h4><p><code>split</code> 필터를 사용하여 지정된 필드를 기준으로 이벤트를 여러 개의 이벤트로 분할할 수 있습니다. 그러나 분할 중에 추가 변환이나 로직을 수행해야 하는 경우 분할 필터와 함께 루비 필터를 사용할 수 있습니다.</p><p>다음 예시에서는 한 줄의 XML 텍스트로 된 RSS 피드가 있습니다. 여기에는 여러 <code>&lt;item&gt;</code> 요소가 포함되어 있습니다. 루비 필터는 XML에서 <code>&lt;item&gt;</code> 요소를 추출하여 <code>items</code> 이라는 새 필드에 저장하는 데 사용됩니다. 그런 다음 분할 필터를 사용하여 <code>items</code> 필드를 기준으로 이벤트를 여러 개의 이벤트로 분할합니다.</p>input {
  generator {
    lines =&gt; [
      '&lt;rss version="2.0"&gt;&lt;channel&gt;&lt;title&gt;Sample RSS&lt;/title&gt;&lt;item&gt;&lt;title&gt;Article 1&lt;/title&gt;&lt;link&gt;http://example.com/1&lt;/link&gt;&lt;description&gt;Desc 1&lt;/description&gt;&lt;/item&gt;&lt;item&gt;&lt;title&gt;Article 2&lt;/title&gt;&lt;link&gt;http://example.com/2&lt;/link&gt;&lt;description&gt;Desc 2&lt;/description&gt;&lt;/item&gt;&lt;/channel&gt;&lt;/rss&gt;'
    ]
    count =&gt; 1
    codec =&gt; "plain"
    ecs_compatibility =&gt; "disabled"
  }
}

filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    code =&gt; "event.set('items', event.get('[rss][channel][item]')) if event.get('[rss][channel][item]')"
  }
  split {
    field =&gt; "items"
  }
  ruby {
    code =&gt; "
      item = event.get('items')
      event.set('title', item['title']) if item['title']
      event.set('link', item['link']) if item['link']
      event.set('description', item['description']) if item['description']
    "
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>다음과 같이 출력됩니다:</p>{
          "title" =&gt; "Article 1",
           "link" =&gt; "http://example.com/1",
    "description" =&gt; "Desc 1"
}
{
          "title" =&gt; "Article 2",
           "link" =&gt; "http://example.com/2",
    "description" =&gt; "Desc 2"
}<p>눈치채셨겠지만, 이 경우 <code>ruby</code> 필터는 필수 항목이 아닙니다. <code>split</code> 필터는 <code>items</code> 필드를 기준으로 이벤트를 여러 개의 이벤트로 분할하는 데 사용할 수 있으며, <code>mutate</code> 필터는 불필요한 필드를 제거하는 데 사용할 수 있습니다. 그러나 분할 중에 추가 변환이나 로직을 수행해야 하는 경우 루비 필터를 사용할 수 있습니다.</p><h4>인라인 루비 스크립트 사용</h4><p>또한 인라인 루비 스크립트를 사용하여 <code>event.clone</code> 메서드와 <code>new_event_block variable</code>, 예: <code>new_event_block.call(new_event)</code> 를 사용하여 단일 이벤트를 여러 개의 이벤트로 분할할 수도 있습니다. 이렇게 하면 데이터를 보존하면서 원래 이벤트를 기반으로 새 이벤트를 만들 수 있습니다.</p><p>다음은 루비 필터를 사용하여 단일 이벤트를 여러 개의 이벤트로 분할하는 방법의 예시입니다. 입력 및 출력은 이전 예제와 동일합니다.</p>filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    code =&gt; "
      items = event.get('[rss][channel][item]')
      if items.is_a?(Array)
        items.each do |item|
          new_event = event.clone
          new_event.set('title', item['title'])
          new_event.set('link', item['link'])
          new_event.set('description', item['description'])
          new_event_block.call new_event
        end
        event.cancel
      elsif items.is_a?(Hash)
        event.set('title', items['title'])
        event.set('link', items['link'])
        event.set('description', items['description'])
      end
    "
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}<h4>외부 루비 스크립트 사용</h4><p>외부 루비 스크립트를 사용하여 단일 이벤트를 여러 개의 이벤트로 분할할 수도 있습니다.</p><p>구성 파일입니다:</p>filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    path =&gt; "path/to/ruby/split_event.rb"
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}<p>루비 스크립트는 <code>split_event.rb</code> 로 외부화해야 합니다:</p>def filter(event)
  items = event.get('[rss][channel][item]')
  events = []
  if items.is_a?(Array)
    items.each do |item|
      new_event = event.clone
      new_event.set('title', item['title'])
      new_event.set('link', item['link'])
      new_event.set('description', item['description'])
      events &lt;&lt; new_event
    end
    return events
  elsif items.is_a?(Hash)
    event.set('title', items['title'])
    event.set('link', items['link'])
    event.set('description', items['description'])
    return [event]
  else
    return []
  end
end<p><code>filter</code> 메서드는 이벤트 배열을 반환해야 한다는 점을 기억하세요. 들어오는 이벤트 객체를 복제하여 배열에 추가하여 여러 이벤트를 반환하거나 단일 이벤트를 하나의 요소가 있는 배열로 반환할 수 있습니다.</p>return events
# or
# return [event]<p>이렇게 하면 하나의 이벤트를 여러 개의 이벤트로 분할할 수 있습니다.</p><h3>외부 명령 실행 및 출력 구문 분석</h3><p>Logstash 실행 입력 플러그인을 사용하면 외부 명령을 실행할 수 있으며, 그 출력은 Logstash의 이벤트가 됩니다. 명령의 출력은 이벤트의 <code>message</code> 필드에 저장됩니다.</p><p>일반적으로 시스템 명령의 출력은 사람이 읽을 수 있지만, Logstash가 쉽게 구문 분석할 수 있는 JSON이나 기타 형식으로 구조화되어 있지 않습니다. 이를 처리하기 위해 루비 필터를 사용하여 출력을 구문 분석하고 거기서 정보를 추출할 수 있습니다.</p><p>다음은 <code>exec</code> 입력 플러그인을 사용하여 유닉스 계열 시스템에서 실행 중인 모든 프로세스를 나열하는 <code>ps -ef</code> 명령을 실행하는 예제입니다. 출력은 루비 필터로 파싱되어 각 프로세스에 대한 관련 정보를 추출합니다.</p>input {
  exec {
    command =&gt; "ps -ef"
    interval =&gt; 60
  }
}

filter {
  ruby {
    code =&gt; '
      processes = []
      lines = event.get("message").split("\n")  
      lines.each_with_index do |line, index|
        # Skip header line and empty lines
        next if index == 0 || line.strip.empty?
        entry = nil
        
        # Use regex to match the ps -ef output format more flexibly
        # This pattern accounts for variable spacing and different time formats
        if line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([\d:]+\.?\d*)\s+(.+)$/
          uid, pid, ppid, c, stime, tty, time, cmd = $1, $2, $3, $4, $5, $6, $7, $8
          
          entry = {
            "UID" =&gt; uid,
            "PID" =&gt; pid,
            "PPID" =&gt; ppid,
            "C" =&gt; c,
            "STIME" =&gt; stime,
            "TTY" =&gt; tty,
            "TIME" =&gt; time,
            "CMD" =&gt; cmd.strip
          }
        elsif line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/
          # Fallback pattern for lines that might not match the exact format
          # Split the remaining part more carefully
          uid, pid, ppid, c, remainder = $1, $2, $3, $4, $5
          
          # Split remainder into STIME, TTY, TIME, CMD
          parts = remainder.strip.split(/\s+/, 4)
          if parts.length &gt;= 4
            stime, tty, time, cmd = parts[0], parts[1], parts[2], parts[3]
            
            entry = {
              "UID" =&gt; uid,
              "PID" =&gt; pid,
              "PPID" =&gt; ppid,
              "C" =&gt; c,
              "STIME" =&gt; stime,
              "TTY" =&gt; tty,
              "TIME" =&gt; time,
              "CMD" =&gt; cmd
            }
          end
        end
        if entry &amp;&amp; entry["UID"] == "0"
          original_line = line.strip
          entry["original_line"] = original_line if original_line.length &gt; 0
          processes.push(entry)
        end
      end
      event.set("processes", processes)
      event.remove("message")
      event.remove("event")
    '
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>이 예에서는 <code>exec</code> 입력 플러그인을 사용하여 60초마다 <code>ps -ef</code> 명령을 실행합니다. 루비 필터는 출력을 처리하여 UID, PID, PPID, CPU 사용량(C), 시작 시간(STIME), TTY, 총 CPU 시간(TIME), 실행된 명령(CMD) 등 관련 필드를 추출합니다. 제 macOS 환경에서는 잘 작동하지만 시스템의 <code>ps -ef</code> 명령의 출력 형식과 일치하도록 정규식 패턴을 조정해야 할 수도 있습니다.</p><h3>기본 제공 라이브러리 사용</h3><p>루비 필터 플러그인을 사용하면 내장된 루비 라이브러리를 사용할 수 있어 다양한 작업에 매우 유용할 수 있습니다. 예를 들어 <code>json</code> 라이브러리를 사용하여 JSON 문자열을 구문 분석하거나 <code>date</code> 라이브러리를 사용하여 날짜를 조작할 수 있습니다.</p><p>다음은 <code>json</code> 라이브러리를 사용하여 필드에 저장된 JSON 문자열을 구문 분석하는 예제입니다:</p>require 'json'

def filter(event)
  json_string = event.get('message')
  parsed_json = JSON.parse(json_string)
  event.set('parsed_json', parsed_json)
  return [event]
end<p>매번 라이브러리가 필요하지 않도록 하려면 Ruby 필터 스크립트의 시작 부분에 <code>require</code> 문을 사용할 수 있도록 Ruby 코드를 외부화해야 합니다. 이렇게 하면 라이브러리가 한 번 로드되고 스크립트에서 사용할 수 있게 됩니다.</p><p>사용자 환경에서 어떤 라이브러리를 사용할 수 있는지 확인하려면 Ruby 필터에서 다음 코드를 실행하여 기본 제공 라이브러리를 나열할 수 있습니다:</p>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end<p><strong>참고: </strong>기본 제공 라이브러리는 Logstash에서 공식적으로 지원되지 않으며, 동작이 변경되거나 향후 버전에서 제공되지 않을 수 있습니다. 본인 책임하에 사용하세요.</p><h2>결론</h2><p>Logstash Ruby 필터를 사용하면 Logstash 파이프라인의 기능을 사용자 정의하고 확장할 수 있습니다. 이 글에서는 루비 필터 사용의 기본 사항을 다루고 고급 사용 예제를 제공했습니다.</p><p>루비 필터를 활용하면 사용자 지정 로직이나 고급 조작이 필요한 복잡한 데이터 처리 작업을 처리할 수 있습니다. 중첩된 데이터 구조로 작업하거나 이벤트를 분할하거나 복잡한/비구조화 텍스트를 구문 분석하여 구조화된 JSON으로 변환하는 등, Ruby 필터는 특정 요구 사항을 충족하는 유연성을 제공합니다.</p><p>이 가이드가 Logstash Ruby 필터의 잠재력을 최대한 활용할 수 있는 지식과 영감을 제공해 주었기를 바랍니다. 행복한 스크립팅!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ruby-scripting-logstash</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ruby-scripting-logstash</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Dai Sugimori]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt503d18396642e73e/6a17f62aaf47b68527cde121/b1bcd63c033ccbde102c20ba3085f165f9289a71-1600x1000.png" length="0" type="image/png"/>
    <pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 인덱스에 필드 표시하기]]></title>
    <description><![CDATA[Elasticsearch 인덱스에서 필드를 표시하는 기술 살펴보기.
]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 Elasticsearch 인덱스에서 필드를 표시하는 방법에 대해 설명합니다. 이는 데이터 구조를 이해하고, 특정 필드를 식별하고, 문제를 해결하는 데 유용할 수 있습니다. 다음 주제를 다룰 예정입니다:</p><ol><li><p><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information"> </a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information"><code>_mapping</code></a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information"> API를 사용하여 필드 정보 검색하기</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values"> API를 </a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values">사용하여 필드 값 표시</a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values"><code>_search</code></a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter">매개 변수를 사용하여 필드 필터링 </a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter"><code>fields</code></a><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter"> </a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#4.-displaying-nested-fields">중첩된 필드 표시</a></p></li></ol><h2>1. 맵핑 API를 사용하여 필드 정보 검색하기</h2><p><code>_mapping</code> API를 사용하면 인덱스 또는 여러 <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-index/">인덱스에</a> 대한 매핑 정의를 검색할 수 있습니다. 여기에는 필드, 데이터 유형 및 기타 속성에 대한 정보가 포함됩니다. 특정 인덱스에 대한 매핑을 검색하려면 다음 요청을 사용하세요:</p>GET /&lt;index_name&gt;/_mapping<p>예를 들어 <code>my_index</code> 이라는 인덱스가 있는 경우 다음 요청으로 해당 인덱스의 매핑을 검색할 수 있습니다:</p>GET /my_index/_mapping<p>응답에는 필드 및 해당 속성에 대한 정보가 포함된 인덱스에 대한 매핑 정의가 포함됩니다.</p><p>특정 필드에 대한 매핑을 검색할 수도 있습니다. 매핑이 상당히 크고 특정 필드에만 집중하려는 경우 유용할 수 있습니다. 특정 필드의 매핑을 검색하려면 다음 요청을 사용하세요:</p>GET /my_index/_mapping/field/my_field<p>다음 요청에서와 같이 쉼표로 이름을 구분하여 여러 필드의 매핑을 검색할 수도 있습니다:</p>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3<h2>2. search API를 사용하여 필드 값 표시하기</h2><p>Elasticsearch 인덱스의 필드 값을 표시하려면 <code>_search</code> API를 사용하면 됩니다. 기본적으로 <code>_search</code> API는 색인된 원본 JSON 문서가 포함된 <code>_source</code> 필드를 반환합니다. 특정 필드만 표시하려면 검색 요청에 <code>_source</code> 매개변수를 사용하면 됩니다.</p><p>다음은 <code>my_index</code> 인덱스에 있는 문서에 대한 <code>title</code> 및 <code>author</code> 필드 값을 반환하는 검색 요청의 예입니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}<p>이 예제에서 <code>_source</code> 매개변수는 반환할 필드를 지정합니다.</p><h2>3. fields 매개변수를 사용하여 필드 필터링하기</h2><p><code>fields</code> 매개변수를 사용하여 검색 응답에 반환되는 필드를 필터링할 수도 있습니다. 특정 필드만 필요하고 응답의 크기를 줄이려는 경우 유용할 수 있습니다. <code>fields</code> 매개변수는 필드 이름 또는 와일드카드 패턴의 배열을 허용합니다.</p><p>예를 들어 <code>my_index</code> 색인에 있는 문서에 대해 <code>title</code> 및 <code>author</code> 필드만 반환하려면 다음 검색 요청을 사용할 수 있습니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}<p>소스 문서를 반환하지 않으려면 <code>_source</code> 매개 변수를 false로 설정해야 합니다.</p><p><code>text</code> 데이터 유형이 있는 모든 필드를 반환하려면 다음과 같은 와일드카드 패턴을 사용할 수 있습니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["*.text"],
  "_source": false
}<h2>4. 중첩된 필드 표시</h2><p>인덱스에 중첩 필드가 포함된 경우, 점 표기법을 사용하여 <code>fields</code> 매개변수에서 중첩 필드 경로를 지정할 수 있습니다. 예를 들어 <code>address.city</code> 이라는 이름의 중첩 필드가 있는 경우 다음과 같이 검색 응답에 포함할 수 있습니다:</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}<p>이 예제에서는 검색 응답에 <code>title</code>, <code>author</code>, <code>address.city</code> 필드의 값이 포함됩니다.</p><h2>결론</h2><p>결론적으로, Elasticsearch 인덱스에서 필드를 표시하려면 <code>_mapping</code> API를 사용하여 필드 정보를 검색하고 <code>_search</code> API를 사용하여 필드 값을 표시할 수 있습니다. <code>_source</code> 또는 <code>fields</code> 매개변수를 사용하여 검색 응답에 반환된 필드를 필터링하고 점 표기법을 사용하여 중첩된 필드를 표시할 수 있습니다. 이러한 기술은 데이터의 구조를 이해하고, 특정 필드를 식별하고, 문제를 해결하는 데 도움이 될 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3a1fcc771a2504e5/6a17f817abe0f23038dfebca/fa386d7bbaeab6855e62897ace8d7dca91a060b4-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[색인에서 Elasticsearch 필드 제외하기]]></title>
    <description><![CDATA[필드를 제외하도록 Elasticsearch를 구성하는 방법, 색인에서 필드를 제외하는 주요 이유, 따라야 할 모범 사례에 대해 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch에서 인덱싱은 데이터를 쉽게 검색할 수 있는 방식으로 저장하고 구성하는 프로세스를 말합니다. 문서의 모든 필드를 색인하는 것이 경우에 따라 유용할 수 있지만, 특정 필드를 색인 대상에서 제외해야 하는 상황이 있을 수 있습니다. 이렇게 하면 성능을 개선하고, 스토리지 비용을 절감하고, Elasticsearch 인덱스의 전체 크기를 최소화하는 데 도움이 됩니다.</p><p>이 문서에서는 색인에서 필드를 제외하는 이유, 특정 필드를 제외하도록 Elasticsearch를 구성하는 방법, 그리고 그렇게 할 때 따라야 할 몇 가지 모범 사례에 대해 설명합니다.</p><h2>인덱싱에서 필드를 제외하는 이유</h2><ol><li><p><strong>성능: </strong>문서의 모든 필드를 색인하면 색인 시간이 길어지고 검색 성능이 저하될 수 있습니다. 검색이나 집계에 필요하지 않은 필드를 제외하면 Elasticsearch 클러스터의 전반적인 성능을 개선할 수 있습니다.</p></li><li><p><strong>저장소: </strong>필드 인덱싱은 저장 공간을 소모합니다. 검색이나 집계에 필요하지 않은 필드를 제외하면 Elasticsearch 클러스터의 저장 공간 요구 사항을 줄이는 데 도움이 될 수 있습니다.</p></li><li><p><strong>인덱스 크기: </strong>Elasticsearch 인덱스의 크기는 색인되는 필드의 수와 직접적으로 관련이 있습니다. 불필요한 필드를 제외하면 색인 크기를 최소화할 수 있어 검색 및 색인 성능이 향상될 수 있습니다.</p></li></ol><h2>필드를 제외하도록 Elasticsearch 구성하기</h2><p>Elasticsearch에서 필드를 색인되지 않도록 제외하려면 필드 매핑에서 "index" 속성을 사용하면 됩니다. "index" 속성을 "false"로 설정하면 Elasticsearch는 필드를 색인하지 않으며, 검색하거나 집계에 사용할 수 없게 됩니다.</p><p>다음은 Elasticsearch 매핑을 사용하여 필드를 색인에서 제외하는 방법의 예입니다:</p>PUT /my_index
{
  "mappings": {
    "properties": {
      "field_to_exclude": {
        "type": "text",
        "index": false
      }
    }
  }
}<p>이 예에서는 "field_to_exclude"라는 단일 필드를 사용하여 "my_index"라는 새 인덱스를 생성합니다. "index" 속성을 "false"로 설정하면, 이 필드를 색인하지 않도록 Elasticsearch에 지시하는 것입니다. 하지만 이 필드는 소스 문서에서 계속 사용할 수 있습니다.</p><h2>인덱싱에서 필드를 제외하는 모범 사례</h2><ol><li><p><strong>데이터 분석하기: </strong>인덱싱에서 필드를 제외하기 전에 데이터를 분석하고 검색 및 집계에 필요한 필드를 파악하는 것이 중요합니다. 이를 통해 제외할 필드에 대해 정보에 입각한 결정을 내릴 수 있습니다.</p></li><li><p><strong>변경 사항을 테스트합니다: </strong>인덱싱에서 필드를 제외할 때는 변경 사항을 테스트하여 검색 및 집계 기능이 여전히 예상대로 작동하는지 확인하는 것이 중요합니다. 이렇게 하면 예기치 않은 문제나 성능 문제를 방지하는 데 도움이 됩니다.</p></li><li><p><strong>성능 모니터링:</strong> 색인에서 필드를 제외시킨 후, Elasticsearch 클러스터의 성능을 모니터링하여 변경 사항이 원하는 효과를 가져왔는지 확인합니다. 이를 통해 필요한 추가 최적화를 파악하는 데 도움이 될 수 있습니다.</p></li><li><p><strong>소스 필터링 사용:</strong> Elasticsearch에 필드를 저장해야 하지만 검색 가능하거나 집계에 사용할 수 없도록 하려면 소스 필터링을 사용하는 것을 고려하세요. 이렇게 하면 _source 필드에 필드를 저장하되 인덱스에서 제외할 수 있습니다.</p></li></ol><h2>결론</h2><p>Elasticsearch에서 색인에서 필드를 제외하면 성능을 개선하고, 저장 비용을 절감하며, 전체 색인 크기를 최소화하는 데 도움이 될 수 있습니다. 데이터를 신중하게 분석하고 검색 및 집계에 필요한 필드를 이해하면 어떤 필드를 제외할지 정보에 입각한 결정을 내릴 수 있습니다. 항상 변경 사항을 테스트하고 Elasticsearch 클러스터의 성능을 모니터링하여 최적화가 원하는 효과를 가져오는지 확인하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/excluding-elasticsearch-fields-from-indexing</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/excluding-elasticsearch-fields-from-indexing</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt399bcc5a2e55bfe0/6a1708b45091684557e1ba3c/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch의 색인 템플릿: 작성 가능한 템플릿을 사용하는 방법]]></title>
    <description><![CDATA[Elasticsearch에서 컴포저블 및 컴포넌트 인덱스 템플릿을 생성해 일관된 매핑을 보장하며 인덱스 구성을 자동화하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>매핑, 설정 및 별칭을 통해 Elasticsearch 인덱스를 구성할 수 있습니다: </p><ul><li><p>매핑 정의는 데이터 스키마를 지정합니다.</p></li><li><p>설정에서 샤드 크기와 새로 고침 빈도를 설정합니다. </p></li><li><p>별칭은 인덱스에 대체 이름을 지정하는 데 사용됩니다.</p></li></ul><p>문서를 처음 색인하거나 색인 생성 API를 사용하여 빈 색인을 만들면 데이터 스키마 및 별칭 없이 기본 설정으로 색인이 생성됩니다. 이러한 기본값은 개발 및 테스트 환경에서는 잘 작동하지만 프로덕션 환경에 맞게 인덱스를 사용자 지정해야 할 수도 있습니다.</p><p>프로덕션 환경에서 기본 매핑 및 설정으로 작업하면 색인 및 검색 성능이 저하될 수 있습니다. 인덱스를 수동으로 인스턴스화하는 작업은 지루하고 시간이 많이 걸리는 과정입니다. 정교한 매핑 스키마와 사용자 정의 설정 및 별칭이 있는 경우 모든 환경에서 이러한 인덱스를 다시 생성하는 것은 특히 비현실적입니다.</p><p>다행히도 Elasticsearch는 <em>인덱스</em> <em>템플릿</em>형태로 인덱스를 생성할 때 미리 정의된 구성을 자동으로 적용할 수 있는 도구를 제공합니다.</p><h2>색인 템플릿</h2><p>인덱스 템플릿을 사용하면 사용자 정의 구성으로 인덱스를 만들 수 있습니다. 인덱스는 인스턴스화 중에 이러한 템플릿에서 설정된 수의 샤드 및 복제본 또는 필드 매핑과 같은 구성을 가져올 수 있습니다. 템플릿은 이름 패턴과 일부 구성으로 정의됩니다. 인덱스의 이름이 템플릿의 명명 패턴과 일치하면 템플릿에 정의된 구성으로 새 인덱스가 생성됩니다.</p><p>Elasticsearch는 버전 7.8에서 구성 가능한 템플릿으로 템플릿 기능을 업그레이드했습니다. 이 최신 버전은 이 문서에서 설명한 대로 훨씬 더 많은 재사용 가능한 인덱스 템플릿을 제공합니다.</p><h3>인덱스 템플릿의 종류</h3><p>색인 템플릿은 두 가지 범주로 분류할 수 있습니다:</p><ul><li><p><strong>색인 템플릿(또는 컴포저블 색인 템플릿)</strong>: 구성 가능한 인덱스 템플릿은 단독으로 존재하거나 하나 이상의 구성 요소 템플릿으로 구성될 수 있습니다(두 번째 범주 참조).</p></li><li><p><strong>컴포넌트 템플릿:</strong> 컴포넌트 템플릿은 필요한 구성을 정의하는 자체적으로 <em>재사용 가능한</em> 템플릿입니다. 일반적으로 컴포넌트 템플릿은 인덱스 템플릿과 연결될 것으로 예상됩니다. 각 컴포넌트 템플릿에는 하나 또는 여러 개의 인덱스 템플릿을 첨부할 수 있습니다. </p></li></ul><p>아래 이미지에서 볼 수 있듯이 인덱스 템플릿 A와 B는 서로 구성 요소 템플릿(이 경우 템플릿 3 하나만)을 공유합니다. 인덱스 템플릿은 하나 또는 여러 개의 구성 요소 템플릿으로 구성될 수 있으며, 각 구성 요소 템플릿은 하나 또는 여러 개의 인덱스 템플릿과 연결될 수 있습니다. 두 가지 유형의 템플릿은 모두 단독으로 존재할 수 있지만 컴포넌트 템플릿은 인덱스 템플릿에 첨부하지 않으면 아무 소용이 없습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7fca132e0eb86c50/6a17f5b7ec0f8982a65a678c/96c0aac29d3992e54a79be34e14cf909e0ca2ea9-1202x556.png" alt="Elasticsearch의 인덱스 템플릿과 그 구성 요소." /><p>일반적인 아이디어는 조직이 다양한 필요에 따라 사용할 수 있도록 구성 요소 템플릿 카탈로그를 개발하고(예: 개별 환경에 맞는 다양한 구성 요소 템플릿 지정), 구성 가능한 인덱스 템플릿을 통해 다양한 인덱스에 이를 첨부하는 것입니다.</p><h2>작성 가능한(색인) 템플릿을 만드는 방법</h2><p>Elasticsearch는 인덱스 템플릿을 관리하기 위한 _index_template 엔드포인트를 제공합니다. 사용자는 이 템플릿에서 인덱스 이름 패턴과 함께 필요한 모든 매핑, 설정 및 별칭을 제공합니다. 주문 생성 로직을 담당하는 마이크로서비스 애플리케이션 <em>고객 주문 서비스에</em> 대한 템플릿을 만드는 예제를 살펴보겠습니다. </p><p>와일드카드가 있는 패턴으로 표시되는 고객 주문에 대한 템플릿을 만들어야 한다고 가정해 보겠습니다: *orders. 이 템플릿에는 주문_날짜 필드, 샤드 및 복제본 번호와 같은 특정 매핑 및 설정이 있을 것으로 예상됩니다.</p><p>인덱스를 생성하는 동안 이 템플릿과 일치하는 모든 인덱스는 이 템플릿에 정의된 구성을 상속합니다. 예를 들어 검은_금요일_주문 인덱스에는 order_date 필드가 있고, 샤드는 5로 설정되며 복제본은 2로 설정됩니다. 이 외에도 이 템플릿에서 생성된 <em>모든</em> 인덱스는 단일 <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-alias/">별칭</a> 이름을 상속받습니다! 주문으로 정의된 인덱스 패턴과 미리 정의된 날짜 형식 dd-MM-yyyy를 가진 단일 oder_date 필드로 구성된 매핑 스키마를 사용하여 이 orders_template을 만들어 보겠습니다. 아래 코드는 이 인덱스 템플릿을 만드는 방법을 보여줍니다.</p>PUT _index_template/orders_template
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    },
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    },
    "aliases":{
      "all_orders":{}
    }
  }
}<p>Kibana의 개발자 도구에서 이 쿼리를 실행하면 미리 정의된 매핑, 설정 및 별칭과 함께 *orders 인덱스 패턴으로 템플릿이 생성됩니다. index_patterns는 일치 패턴의 배열로, 이 패턴과 일치하는 인덱스는 템플릿 구성을 도출합니다. 다음을 실행하여 우리가 수행한 작업을 반복해야 하는 지속된 템플릿을 검색할 수 있습니다:</p>GET _index_template/orders_template <p>템플릿에 정의된 템플릿 속성을 만들 때 양수인 우선순위를 정의할 수도 있습니다. 모든 템플릿에는 우선순위가 정의되어 있으므로 다른 템플릿에서 충돌하는 변경 사항이 있을 경우 우선순위가 높은 값을 우선하여 이 값을 사용하여 해결됩니다. 아래에서 템플릿 우선 순위에 대해 자세히 알아보겠습니다.</p><h2>템플릿으로 색인 만들기</h2><p>이제 인덱스를 만들기 위한 청사진인 템플릿이 생겼으니 다음 단계는 인덱스를 만드는 것입니다. 인덱스 이름이 지정된 패턴과 일치하면 템플릿 구성이 자동으로 적용됩니다. 이를 증명하기 위해 아래 코드에서 볼 수 있듯이 검은 금요일_주문이라는 새로운 인덱스를 만들어 보겠습니다:</p>PUT blackfriday_orders<p>인덱스 이름(blackfriday_orders)이 템플릿에 정의된 명명 패턴과 일치하므로(예를 들어 *주문), 인덱스는 템플릿에서 파생된 모든 구성을 가져와야 합니다. 새로 생성된 인덱스를 검색하고 다음 코드를 실행하여 이것이 실제로 사실인지 확인해 보겠습니다:</p>GET blackfriday_orders<p>반환됩니다:</p>{
  "blackfriday_orders" : {
    "aliases" : {
      "all_orders" : { }
    },
    "mappings" : {
      "properties" : {
        "order_date" : {
          "type" : "date",
          "format" : "dd-MM-yyyy"
        }
      }
    },
    "settings" : {
      "index" : {
         ...
        "number_of_shards" : "5",
        "number_of_replicas" : "2"
      }
    }
  }
}<p>응답에서 알 수 있듯이 검은 금요일 주문의 구성은 템플릿에서 상속되었습니다. 템플릿 구성을 성공적으로 상속할 수 있는 다양한 인덱스 조합을 시도해 볼 수 있습니다:</p>PUT blackfriday_orders
PUT americaorders
PUT cancelled--orders
PUT undefined101orders<p>그러나 다음 인덱스는 이름이 패턴과 일치하지 않으므로 구성을 상속하지 않습니다:</p>PUT blackfriday_orders2
PUT open_orders_
PUT allorders_total<p>기억해야 할 한 가지 중요한 점은 템플릿에서 파생된 모든 인덱스는 동일한 별칭(이 경우 all_orders)을 사용한다는 것입니다. 이러한 별칭을 사용하면 여러 인덱스가 아닌 이 단일 별칭으로 간단히 쿼리할 수 있다는 이점이 있습니다.</p>GET blackfriday_orders,americaorders,undefined101orders/_search
GET all_orders/_search 
{
  "query": {
    "range": {
      "order_date": {
        "gte": "01-12-2021",
        "lte": "31-12-2021"
      }
    }
  }
}<p>주문에 대한 템플릿을 생성하는 동안 일치하는 인덱스는 모두 템플릿 구성을 채택할 것으로 예상됩니다. 일반적으로 팀에서는 자의든 타의든 여러 가지 이유로 템플릿을 몇 개 더 만들 수 있습니다. 즉, 인덱스 이름이 두 개의 다른 템플릿 패턴과 일치하는 경우가 있습니다! Elasticsearch는 이러한 템플릿에서 어떤 구성을 적용해야 할지 결정해야 합니다. 다행히도 템플릿 우선순위를 사용하면 이 딜레마를 해결할 수 있습니다.</p><h2>구성 요소 템플릿을 생성하는 방법</h2><p>이 글의 앞부분에서 인덱스 템플릿에 대해 알아보았습니다. 구성이 내장된 템플릿을 만들면 몇 가지 단점이 있는데, 그 중 하나는 다른 템플릿으로 구성을 내보낼 수 없다는 점입니다. 고객 관련 템플릿(*고객)과 같이 유사한 구성을 원할 경우 전체 템플릿을 다시 만들어야 할 수도 있습니다. 즉, 일반적인 조직에서는 수십 개를 만들 수 있습니다(환경에 따라 몇 개 더 만들 수도 있습니다).</p><p>항상 재사용 가능성을 염두에 두고 템플릿을 재설계하기 때문에 Elasticsearch는 재사용 가능성을 염두에 두고 템플릿을 재설계했습니다. 컴포넌트 템플릿이 이에 적합합니다. DevOps 출신이라면 각 환경에 대해 미리 설정된 구성으로 인덱스를 만들어야 하는 요구 사항이 있을 것입니다. 이러한 각 구성을 수동으로 적용하는 번거로움 대신 각 환경에 대한 컴포넌트 템플릿을 만들 수 있습니다.</p><p>구성 요소 템플릿은 더 많은 인덱스 템플릿을 구성하는 데 사용할 수 있는 재사용 가능한 구성 블록에 불과합니다. 컴포넌트 템플릿은 인덱스 템플릿과 클럽화하지 않으면 아무런 가치가 없습니다. 구성 요소 템플릿 엔드포인트를 통해 노출됩니다. 이 모든 것이 어떻게 결합되는지 살펴봅시다.</p><h3>인덱스 템플릿의 설정</h3><p>앞서 인덱스 템플릿에서 정의한 설정을 추출하여 컴포넌트 템플릿을 만들어 보겠습니다. 설정_컴포넌트_템플릿에는 기본 샤드당 2개의 복제본이 있는 5개의 기본 샤드가 있어야 합니다. 아래 코드 목록에서 볼 수 있듯이 첫 번째 단계는 이 구성으로 컴포넌트 템플릿을 선언하고 실행하는 것입니다.</p>PUT _component_template/settings_component_template
{
  "template":{
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    }
  }
}<p>위의 코드에서 볼 수 있듯이 _component_template 엔드포인트를 사용하여 컴포넌트 템플릿을 생성합니다. 요청 본문에는 템플릿 객체에 템플릿 정보가 들어 있습니다. 이제 색인 템플릿의 다른 곳에서 settings_component_template을 사용할 수 있습니다. 한 가지 주목할 만한 차이점은 이 템플릿은 인덱스 패턴을 정의하지 않고 일부 속성을 구성하는 코드 블록에 불과하다는 점입니다.</p><h3>매핑 템플릿</h3><p>같은 방법으로 다른 템플릿을 만들어 보겠습니다. 이번에는 앞서 독립형 인덱스 템플릿에서 정의했던 매핑 스키마를 추출해 보겠습니다. 아래 코드는 스크립트를 보여줍니다:</p>PUT _component_template/mappings_component_template
{
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    }
  }
}<h3>별칭 템플릿</h3><p>동일한 흐름에 따라 별칭이 있는 컴포넌트 템플릿(두 개의 별칭(all_orders 및 sales_orders))을 가질 수도 있습니다:</p>PUT _component_template/aliases_component_template
{
  "template": {
    "aliases": {
      "all_orders": {},
      "sales_orders":{}
    }
  }
}<h3>작성 가능한 색인 템플릿</h3><p>이제 세 가지 컴포넌트 템플릿을 준비했으니 다음 단계는 이를 사용하는 것입니다. 예를 들어 christmas_orders에 대한 인덱스 템플릿을 사용하도록 허용하면 이 작업을 수행할 수 있습니다:</p>PUT _index_template/composed_orders_template
{
  "index_patterns": [
    "*orders"
  ],
  "priority": 500,
  "composed_of": [
    "settings_component_template",
    "mappings_component_template",
    "aliases_component_template"
  ]
}<p>composed_of 태그는 이 템플릿을 구성하는 모든 컴포넌트 템플릿의 모음입니다. 이 경우 설정, 매핑 및 별칭 컴포넌트 템플릿을 선택합니다. 또한 이 템플릿이 다른 템플릿보다 우선하도록 우선순위를 높이고 있습니다. 템플릿이 준비되면 *주문 패턴과 일치하는 모든 인덱스는 이 세 가지 구성 요소 템플릿의 구성을 상속받게 됩니다.</p><p>하지만 고객과 같은 새 템플릿을 만들려면 기존 템플릿(settings_component_template)과 새로 만든 별칭(aliases_component_template - 아래 참조) 템플릿 중 하나만 사용하여 만들 수 있습니다:</p>PUT _component_template/aliases_component_template2
{
  "template": {
    "aliases": {
      "all_customers": {}
    }
  }
}<p>인덱스 템플릿은 다음과 같습니다:</p>PUT _index_template/composed_customers_template
{
  "index_patterns": [
    "*customers*"
  ],
  "priority": 200,
  "composed_of": [
    "settings_component_template",
    "aliases_component_template2"
  ]
}<p>설정_컴포넌트_템플릿이 두 개의 다른 템플릿에서 (재)사용된 것을 보셨나요? 이것이 바로 컴포넌트 템플릿의 힘입니다.</p><h2>인덱스 템플릿 우선순위</h2><p>개발자가 기존 종목을 보지 않고 여러 개의 인덱스 템플릿을 만들 수 있습니다. 이러한 템플릿 각각에 우선순위를 설정하여 우선순위가 높은 템플릿이 사용되도록 하는 것이 중요합니다. 예를 들어, 다음 코드 스니펫에서 my_orders_template_1이 my_orders_template_2를 재정의합니다:</p>PUT _index_template/my_orders_template_1
{
  "index_patterns": ["*orders"],
  "priority": 1000,
  "template": { ... }
}
PUT _index_template/my_orders_template2
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": { ... }
}<p>생성 중인 인덱스와 일치하는 템플릿이 여러 개 있는 경우, Elasticsearch는 일치하는 모든 템플릿의 모든 구성을 적용하지만 우선순위가 더 높은 모든 구성을 재정의합니다.</p><h2>템플릿의 우선 순위</h2><p>마지막으로 템플릿의 우선순위에 대해 궁금하실 텐데요, 구성 요소 템플릿에 정의된 구성이 기본 인덱스 템플릿 자체에 정의된 구성보다 우선하나요? 아니면 그 반대일까요? 몇 가지 규칙이 있습니다:</p><ul><li><p>즉, 명시적으로 구성하여 만든 인덱스는 모든 항목보다 우선합니다. 즉, 명시적으로 구성하여 인덱스를 만들면 템플릿에 의해 재정의되지 않을 것으로 기대하지 마세요.</p></li><li><p>레거시 템플릿(버전 7.8 이전에 만든 템플릿)은 작성 가능한 템플릿보다 우선 순위가 낮습니다.</p></li></ul><h2>요약</h2><ul><li><p>인덱스에는 매핑, 설정 및 별칭이 포함됩니다. 매핑은 필드 스키마를 정의하고, 설정은 샤드 및 복제본 수와 같은 인덱스 파라미터를 설정하며, 별칭은 인덱스에 대체 이름을 부여합니다.</p></li><li><p>템플릿을 사용하면 미리 정의된 구성으로 인덱스를 만들 수 있습니다. 특정 템플릿에 정의된 인덱스 패턴과 일치하는 이름으로 인덱스 이름을 지정하면 템플릿에 따라 해당 인덱스가 자동으로 구성됩니다.</p></li><li><p>Elasticsearch는 버전 7.8에서 구성 가능한 인덱스 템플릿을 도입했습니다. 구성 가능한 인덱스 템플릿을 사용하면 템플릿을 모듈화하고 버전을 관리할 수 있습니다.</p></li><li><p>컴포저블 템플릿은 하나 이상의 컴포넌트 템플릿으로 구성됩니다.</p></li><li><p>인덱스 템플릿에는 자체 구성도 정의할 수 있습니다.</p></li><li><p>컴포넌트 템플릿은 작성 가능한 인덱스 템플릿과 마찬가지로 미리 정의된 구성이 있는 재사용 가능한 템플릿입니다.</p></li><li><p>그러나 구성 요소 템플릿은 인덱스 템플릿의 일부가 되어야 하며, 인덱스 템플릿으로 '구성'되지 않으면 쓸모가 없습니다.</p></li><li><p>구성 요소 템플릿에는 인덱스 패턴이 정의되어 있지 않으므로 인덱스 템플릿의 일부가 될 것으로 '예상'되는 또 다른 이유입니다.</p></li><li><p>각 템플릿에는 양수인 우선순위가 있습니다. 숫자가 높을수록 해당 템플릿이 적용되는 우선 순위가 높아집니다.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/index-composable-templates</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/index-composable-templates</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt98737a72caa74fe4/6a17f5b84b055d278d43236a/510750708df50bf79463586a1bbf35bf94acfa30-1200x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Airbyte를 통해 Elasticsearch로 데이터를 수집하는 방법]]></title>
    <description><![CDATA[Airbyte를 사용해 Elasticsearch로 데이터를 수집합니다. 전제 조건, Airbyte 구성 및 단계별 통합에 대해 설명합니다.]]></description>
    <content:encoded><![CDATA[<p>Airbyte는 다양한 소스에서 다양한 대상으로 정보를 자동화되고 확장 가능한 방식으로 이동할 수 있는 데이터 통합 도구입니다. API, 데이터베이스 및 기타 시스템에서 데이터를 추출하여 고급 검색과 효율적인 분석을 제공하는 Elasticsearch와 같은 플랫폼으로 로드할 수 있습니다.</p><p>이 문서에서는 주요 개념, 전제 조건 및 단계별 통합을 다루면서 Elasticsearch로 데이터를 수집하도록 Airbyte를 구성하는 방법에 대해 설명합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99eabff95c587c00/6a17e300e8fbce2d303a18a9/ea7af907dfd4c0b7e8673164467ee236623282d2-1360x802.png" alt="데이터를 Elasticsearch로 수집하도록 Airbyte 구성하기" /><h2>에어바이트 기본 개념</h2><p>에어바이트에는 몇 가지 필수 개념이 있습니다. 아래에서는 주요 내용을 강조합니다:</p><ul><li><p>소스: 추출할 데이터의 출처를 정의합니다.</p></li><li><p>대상: 대상: 데이터를 전송하고 저장할 위치를 정의합니다.</p></li><li><p>연결: 동기화 빈도를 포함하여 소스와 대상 간의 관계를 구성합니다.</p></li></ul><h2>Airbyte와 Elasticsearch 통합</h2><p>이 데모에서는 S3 버킷에 저장된 데이터를 Elasticsearch 인덱스로 마이그레이션하는 통합을 수행합니다. Airbyte에서 소스(S3)와 대상(Elasticsearch)을 구성하는 방법을 보여드리겠습니다.</p><h3>필수 구성 요소</h3><p>이 데모를 따라하려면 다음 전제 조건을 충족해야 합니다:</p><ol><li><p>데이터가 포함된 JSON 파일이 저장될 버킷을 AWS에 생성합니다.</p></li><li><p>Docker를 사용하여 <a href="https://docs.airbyte.com/using-airbyte/getting-started/oss-quickstart">로컬에 Airbyte를 설치합니다</a>.</p></li><li><p>수집된 데이터를 저장하기 위해 Elastic Cloud에서 Elasticsearch 클러스터를 생성합니다.</p></li></ol><p>아래에서 이러한 각 단계에 대해 자세히 설명합니다.</p><h4>Airbyte 설치</h4><p>Airbyte는 Docker를 사용하여 로컬에서 실행하거나 사용 비용이 발생하는 클라우드에서 실행할 수 있습니다. 이 데모에서는 Docker가 포함된 로컬 버전을 사용하겠습니다.</p><p>설치하는 데 몇 분 정도 걸릴 수 있습니다. 설치 지침을 따르고 나면 Airbyte를 http://localhost:8000 에서 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f9149be887b5500/6a17e302abe0f2b1c6dfe956/66147b5121413ad9baecb10c6886288e917f7c09-1600x1102.png" alt="Airbyte 설치" /><p></p><p>로그인한 후 연동 구성을 시작할 수 있습니다.</p><h4>버킷 만들기</h4><p>이 단계에서는 S3 버킷을 생성하기 위해 AWS 계정이 필요합니다. 또한 버킷에 대한 액세스를 허용하는 정책과 IAM 사용자를 만들어 올바른 권한을 설정하는 것이 중요합니다.</p><p>버킷에 다양한 로그 레코드가 포함된 JSON 파일을 업로드하고 나중에 Elasticsearch로 마이그레이션할 것입니다. 파일 로그에는 이 내용이 있습니다:</p>{
   "timestamp": "2025-02-15T14:00:12Z",
   "level": "INFO",
   "service": "data_pipeline",
   "message": "Pipeline execution started",
   "details": {
       "pipeline_id": "abc123",
       "source": "MySQL",
       "destination": "Elasticsearch"
   }
}<p>아래는 버킷에 로드된 파일입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf769c5e9bdb5dfe/6a17e3043e9e45a360ba13f7/f3e7f5889002e3a804121a880d97f1d93044e2f7-1600x680.png" alt="Airbyte의 버킷에 로드된 파일" /><h4>Elastic Cloud 구성</h4><p>데모를 더 쉽게 하기 위해 Elastic Cloud를 사용하겠습니다. 아직 계정이 없는 경우 여기에서 무료 체험판 계정을 생성할 수 있습니다: <a href="https://cloud.elastic.co/registration">Elastic Cloud 등록</a>.</p><p>Elastic Cloud에서 배포를 구성한 후에는 다음과 같은 정보를 얻어야 합니다:</p><ul><li><p>Elasticsearch 서버의 URL입니다.</p></li><li><p>Elasticsearch에 액세스하는 사용자입니다.</p></li></ul><p>URL을 얻으려면 배포 &gt; 내 배포로 이동하여 애플리케이션에서 Elasticsearch를 찾아 '엔드포인트 복사'를 클릭합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltddfaaf863168e2bb/6a17e305faa913e29793c7e4/2e38c0bfb2cea83d9ef90dba0e673559fe358199-1368x1056.png" alt="Elastic Cloud 구성" /><p>사용자를 만들려면 아래 단계를 따르세요:</p><ol><li><p>Kibana에 액세스 &gt; 스택 관리 &gt; 사용자.</p></li><li><p>수퍼유저 역할을 가진 새 사용자를 만듭니다.</p></li><li><p>필드를 채워 사용자를 만듭니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca15b0d3084f4c67/6a17e3073e9e4507e2ba13fb/54d098805087a2d60772475cbb31784083a38c25-1600x1026.png" alt="Elastic Cloud에서 사용자 생성" /><p>이제 모든 설정이 완료되었으므로 Airbyte에서 커넥터 구성을 시작할 수 있습니다.</p><h3>소스 커넥터 구성</h3><p>이 단계에서는 S3용 소스 커넥터를 만들겠습니다. 이를 위해 Airbyte 인터페이스에 액세스하고 메뉴에서 소스 옵션을 선택합니다. 그런 다음 S3 커넥터를 검색합니다. 아래에서는 커넥터를 구성하는 데 필요한 단계를 자세히 설명합니다:</p><ol><li><p>Airbyte에 액세스하고 소스 메뉴로 이동합니다.</p></li><li><p>S3 커넥터를 검색하여 선택합니다.</p></li><li><p>다음 매개변수를 구성합니다:</p><ol><li><p>소스 이름: 데이터 소스의 이름을 정의합니다.</p></li><li><p>전달 방법: 레코드 복제를 선택합니다(구조화된 데이터에 권장).</p></li><li><p>데이터 형식: JSON 형식을 선택합니다.</p></li><li><p>스트림 이름: Elasticsearch에서 인덱스의 이름을 정의합니다.</p></li><li><p>버킷 이름: AWS에서 버킷의 이름을 입력합니다.</p></li><li><p>AWS 액세스 키와 AWS 비밀 키를 입력합니다: 액세스 자격 증명을 입력합니다.</p></li></ol></li></ol><p>소스 설정을 클릭하고 유효성 검사를 기다립니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdca1fb8d8f5d034f/6a17e30963baff75bc741bb2/f83566ab5ebad07ad9fd61dc20353ca5a95c8c92-1600x1099.png" alt="Airbyte 및 Elasticsearch 데이터 수집에 대한 유효성 검사를 기다립니다." /><h3>구성 대상 커넥터</h3><p>이 단계에서는 대상 커넥터를 구성할 것이며, 이 커넥터는 Elasticsearch가 될 것입니다. 이렇게 하려면 메뉴에 액세스하여 대상 옵션을 선택합니다. 그런 다음 Elasticsearch를 검색하고 반환된 결과를 클릭합니다. 이제 이 연결의 구성을 진행하겠습니다:</p><ol><li><p>Airbyte에 액세스하고 목적지 메뉴로 이동합니다.</p></li><li><p>Elasticsearch 커넥터를 검색하고 선택합니다.</p></li><li><p>다음 매개변수를 구성합니다:</p><ol><li><p>인증 방법: 사용자 이름/비밀번호를 선택합니다.</p></li><li><p>사용자 이름 및 비밀번호: Kibana에서 생성한 자격 증명을 사용합니다.</p></li><li><p>서버 엔드포인트: Elastic Cloud에서 복사한 URL을 붙여넣습니다.</p></li></ol></li></ol><p><strong>대상 설정을</strong> 클릭하고 유효성 검사를 기다립니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72056179d048e027/6a17e30b033c8d5d9d6bb115/f9246b54cc77e0589c0bf658ffc274fd28f766d5-1600x941.png" alt="Airbyte 데이터 수집을 위해 Elastic Cloud에서 대상 만들기" /><h3>소스 및 대상 연결 만들기</h3><p>소스 및 대상이 생성되면 소스 및 대상 간의 연결이 생성되어 통합 생성이 완료됩니다. </p><p>다음은 연결을 만드는 방법에 대한 안내입니다:</p><p>1. 메뉴에서 연결로 이동하여 첫 번째 연결 만들기를 클릭합니다.</p><p>2. 다음 화면에서 기존 소스를 선택하거나 새 소스를 만들 수 있습니다. 이미 생성한 소스가 있으므로 소스 S3를 선택합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7f1e01215a7a328/6a17e30c63baff53d7741bb6/14937ccb2686bbde7cb99ffe7e13f7f4e35d7a31-1600x393.png" alt="Airbyte에서 기존 소스를 선택하거나 새 소스를 생성합니다." /><p>3. 다음 단계는 목적지를 선택하는 것입니다. 이미 Elasticsearch 커넥터를 생성했으므로 구성을 완료하기 위해 이 커넥터가 선택됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltece5c720f28eee00/6a17e30d505ac3dc68ad8aa3/d10962bc175f9ce0b2745ea913d739d3c40ba3b6-1600x431.png" alt="Airbyte에서 목적지 선택" /><p>다음 단계에서는 동기화 모드와 어떤 스키마를 사용할지 정의해야 합니다. 로그 스키마만 생성되었으므로 선택할 수 있는 옵션은 로그 스키마가 유일합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7cbcccad9cfd5a8d/6a17e30f414c64d32d9450e9/d5d3a2e20038d17ef701822fe7ccc38d6e175c50-1600x805.png" alt="에어바이트에서 동기화 모드 정의" /><p>4. 연결 구성 단계로 이동합니다. 여기에서 연결 이름과 통합 실행 빈도를 정의할 수 있습니다. 주파수는 세 가지 방법으로 구성할 수 있습니다:</p><ul><li><p><strong>Cron</strong>: 사용자 정의 크론 표현식(예: 0 0 15 * * ?, 매일 15:00에)에 따라 동기화를 실행합니다;</p></li><li><p><strong>예약됨</strong>: 지정된 시간 간격으로 동기화를 실행합니다(예 24시간마다, 2시간마다);</p></li><li><p><strong>수동</strong>: 수동으로 동기화를 실행합니다.</p></li></ul><p>이 데모에서는 수동 옵션을 선택하겠습니다.</p><p>마지막으로 <strong>연결 설정을</strong> 클릭하면 소스와 대상 간의 연결이 설정됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f5fc0e51035b733/6a17e311af47b67ac8cddeff/1a0470f6dff341cb90e6ecfd8ae87a7d2243cbf4-1600x626.png" alt="Airbyte에서 연결 설정을 클릭합니다." /><h3>S3에서 Elasticsearch로 데이터 동기화하기</h3><p>연결 화면으로 돌아오면 생성된 연결을 확인할 수 있습니다. 프로세스를 실행하려면 동기화를 클릭하기만 하면 됩니다. 그 순간부터 S3에서 Elasticsearch로 데이터 마이그레이션이 시작됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b0ad7a7446f9d58/6a17e3121d1b83b4c593e3c3/c1ce54b129eafb2533b638e4df2b96f3a266ad62-1600x347.png" alt="S3에서 Airbyte의 Elasticsearch로 데이터 동기화하기" /><p>모든 것이 순조롭게 진행되면 동기화 상태가 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857cdad5bd7fe03f/6a17e313be608646e00046b9/6fe9d5826787066bd8bf0f5e17905df9f8d698e6-1600x361.png" alt="Airbyte의 S3에서 Elasticsearch로 동기화된 상태" /><h3>Kibana에서 데이터 시각화하기</h3><p>이제 Kibana로 이동하여 데이터를 분석하고 데이터가 올바르게 색인되었는지 확인하겠습니다. Kibana Discovery 섹션에서 로그라는 데이터 보기를 생성합니다. 이를 통해 동기화 이후에 생성된 로그 인덱스에만 존재하는 데이터를 탐색할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bc137f7c04e50ee/6a17e3151480094f2eb486cf/6b6909647ac3187d0fee477cfd732741314f82cf-1600x566.png" alt="Kibana에서 데이터 시각화하기: Airbyte와 Elastic" /><p>이제 색인된 데이터를 시각화하고 분석을 수행할 수 있습니다. 이렇게 해서 Airbyte를 사용해 전체 마이그레이션 흐름을 검증하고, 버킷에 있는 데이터를 로드하고 Elasticsearch에서 색인을 생성했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8c0a36a83ee4bb1/6a17e317b1e1135ea679f20e/ca8e9b3d7f7112291af58acf514f4573b44037e8-1600x806.png" alt="Airbyte와 Elastic: Kibana에서 색인된 데이터를 시각화하고 분석을 수행합니다." /><h2>결론: Airbyte &amp; Elasticsearch 통합</h2><p>Airbyte는 데이터 통합을 위한 효율적인 도구로 여러 소스와 대상을 자동화된 방식으로 연결할 수 있는 것으로 입증되었습니다. 이 튜토리얼에서는 S3 버킷에서 Elasticsearch 인덱스로 데이터를 수집하는 방법을 시연하면서 프로세스의 주요 단계를 강조했습니다.</p><p>이 접근 방식은 대량의 데이터 수집을 용이하게 하고 복잡한 검색, 집계, 데이터 시각화와 같은 분석을 Elasticsearch 내에서 수행할 수 있게 해줍니다.</p><h2>참고 자료</h2><p><strong>에어바이트 퀵스타트:</strong></p><p><a href="https://docs.airbyte.com/using-airbyte/getting-started/oss-quickstart#part-1-install-abctl">https://docs.airbyte.com/using-airbyte/getting-started/oss-quickstart#part-1-install-abctl</a></p><p><strong>핵심 개념:</strong></p><p><a href="https://docs.airbyte.com/using-airbyte/core-concepts/">https://docs.airbyte.com/using-airbyte/core-concepts/</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/airbyte-elasticsearch-ingest-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/airbyte-elasticsearch-ingest-data</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5defe5e12935b233/6a17e318505ac3ed7fad8aa7/dce2bad9949006163af95ed05b5a1eacf5393dc7-1200x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LlamaIndex를 통해 Elasticsearch로 데이터를 수집하는 방법]]></title>
    <description><![CDATA[라마인덱스를 사용하여 데이터를 수집하고 검색하는 방법에 대한 단계별 안내입니다.]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 데이터 색인을 생성하기 위해 LlamaIndex를 사용하여 FAQ 검색 엔진을 구현해 보겠습니다. Elasticsearch는 벡터 데이터베이스 역할을 하여 벡터 검색을 가능하게 하고, RAG(검색 증강 세대)는 컨텍스트를 보강하여 보다 정확한 응답을 제공합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5895fbc057ffc1b/6a17f4cf3e9e45288bba15ef/7ac65a686bdd76c145e903f5c3110c62875a525f-972x501.png" alt="LlamaIndex &amp; Elasticsearch: 문서 수집 및 FAQ 검색 구축" /><h2>라마인덱스란 무엇인가요?</h2><p>LlamaIndex는 특정 또는 비공개 데이터와 상호 작용할 수 있는 대규모 언어 모델(LLM)로 구동되는 에이전트 및 워크플로우를 쉽게 만들 수 있는 프레임워크입니다. 다양한 소스(API, PDF, 데이터베이스)의 데이터를 LLM과 통합하여 연구, 정보 추출, 문맥에 맞는 응답 생성 등의 작업을 수행할 수 있습니다.</p><p><strong>주요 개념:</strong></p><ul><li><p>에이전트: 간단한 응답부터 복잡한 작업까지 다양한 작업을 수행하기 위해 LLM을 사용하는 지능형 어시스턴트입니다.</p></li><li><p>워크플로우: 고급 작업을 위해 에이전트, 데이터 커넥터 및 도구를 결합하는 다단계 프로세스입니다.</p></li><li><p>컨텍스트 증강: 외부 데이터로 LLM을 보강하여 학습의 한계를 극복하는 기술입니다.</p></li></ul><p>엘라스틱서치와<strong>라마인덱스</strong> <strong>통합:</strong></p><p>Elasticsearch는 LlamaIndex와 함께 다양한 방식으로 사용할 수 있습니다:</p><ul><li><p>데이터 소스: Elasticsearch Reader를 사용하여 문서를 추출합니다.</p></li><li><p>임베딩 모델: 시맨틱 검색을 위해 데이터를 벡터로 인코딩합니다.</p></li><li><p>벡터 저장소: 벡터화된 문서를 검색하기 위한 리포지토리로 Elasticsearch를 사용하세요.</p></li><li><p>고급 스토리지: 문서 요약 또는 지식 그래프와 같은 구조를 구성하세요.</p></li></ul><h2>LlamaIndex와 Elasticsearch를 사용하여 FAQ 검색 구축하기 </h2><h3>데이터 준비</h3><p><a href="https://www.elastic.co/guide/en/cloud/current/ec-faq-getting-started.html">Elasticsearch 서비스 FAQ를</a> 예로 들어보겠습니다. 각 문제는 웹사이트에서 추출하여 개별 텍스트 파일에 저장했습니다. 어떤 방식으로든 데이터를 정리할 수 있지만, 이 예에서는 파일을 로컬에 저장하는 방법을 선택했습니다.</p><p>예제 파일입니다:</p>File Name: what-is-elasticsearch-service.txt
Content: Elasticsearch Service is hosted and managed Elasticsearch and Kibana brought to you by the creators of Elasticsearch. Elasticsearch Service is part of Elastic Cloud and ships with features that you can only get from the company behind Elasticsearch, Kibana, Beats, and Logstash. Elasticsearch is a full text search engine that suits a range of uses, from search on websites to big data analytics and more.<p>모든 문제를 저장한 후 디렉토리는 다음과 같이 표시됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43467eb9c5579103/6a17f4d02f4a5cc60bfa8a62/1f367d57f2e650334671a2c03156ef4412c0c615-962x704.png" alt="" /><h3>종속성 설치</h3><p>Python 언어를 사용하여 수집 및 검색을 구현할 것이며, 제가 사용한 버전은 3.9입니다. 전제 조건으로 다음 종속성을 설치해야 합니다:</p>llama-index-vector-stores-elasticsearch
llama-index
openai<p>Elasticsearch와 Kibana는 버전 8.16.2를 실행하도록 docker-compose.yml을 통해 구성된 Docker로 생성됩니다. 이렇게 하면 로컬 환경을 더 쉽게 만들 수 있습니다.</p>version: '3.8'
services:

 elasticsearch:
   image: docker.elastic.co/elasticsearch/elasticsearch:8.16.2
   container_name: elasticsearch-8.16.2
   environment:
     - node.name=elasticsearch
     - xpack.security.enabled=false
     - discovery.type=single-node
     - "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
   ports:
     - 9200:9200
   networks:
     - shared_network

 kibana:
   image: docker.elastic.co/kibana/kibana:8.16.2
   container_name: kibana-8.16.2
   restart: always
   environment:
     - ELASTICSEARCH_URL=http://elasticsearch:9200
   ports:
     - 5601:5601
   depends_on:
     - elasticsearch
   networks:
     - shared_network

networks:
 shared_network:<h3>LlamaIndex를 사용한 문서 수집</h3><p>문서는 LlamaIndex를 사용하여 Elasticsearch로 색인됩니다. 먼저 로컬 디렉터리에서 파일을 로드할 수 있는 <strong>SimpleDirectoryReader를</strong> 사용하여 파일을 로드합니다. 문서를 로드한 후 <strong>벡터스토어인덱스를</strong> 사용하여 색인을 생성합니다.</p>documents = SimpleDirectoryReader("./faq").load_data()

storage_context = StorageContext.from_defaults(vector_store=es)
index = VectorStoreIndex(documents, storage_context=storage_context, embed_model=embed_model)<p>라마인덱스의 벡터 스토어는 문서 임베딩의 저장 및 관리를 담당합니다. LlamaIndex는 다양한 유형의 벡터 저장소를 지원하며, 이 경우에는 Elasticsearch를 사용하겠습니다. StorageContext에서 Elasticsearch 인스턴스를 구성합니다. 컨텍스트가 로컬이므로 추가 매개 변수가 필요하지 않았습니다. 다른 환경에서의 구성은 설명서를 참조하여 필요한 매개 변수를 확인하세요: <a href="https://docs.llamaindex.ai/en/stable/examples/vector_stores/ElasticsearchIndexDemo/#configuring-elasticsearchstore">ElasticsearchStore 구성</a>.</p><p>기본적으로 LlamaIndex는 OpenAI <strong>텍스트 임베딩-ada-002</strong> 모델을 사용하여 임베딩을 생성합니다. 하지만 이 예제에서는 <strong>텍스트 임베딩 3-소형</strong> 모델을 사용합니다. 이 모델을 사용하려면 OpenAI API 키가 필요하다는 점에 유의하세요.</p><p>아래는 문서 수집을 위한 전체 코드입니다.</p>import openai
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.vector_stores.elasticsearch import ElasticsearchStore

openai.api_key = os.environ["OPENAI_API_KEY"]

es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200"
)

def format_title(filename):
   filename_without_ext = filename.replace('.txt', '')
   text_with_spaces = filename_without_ext.replace('-', ' ')
   formatted_text = text_with_spaces.title()

   return formatted_text


embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = SimpleDirectoryReader("./faq").load_data()

for doc in documents:
   doc.metadata['title'] = format_title(doc.metadata['file_name'])

storage_context = StorageContext.from_defaults(vector_store=es)
index = VectorStoreIndex(documents, storage_context=storage_context, embed_model=embed_model)<p>실행 후 문서가 아래와 같이 <strong>FAQ</strong> 색인에 색인됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt391ae1bfc1daefbf/6a17f4d22f4a5c9887fa8a66/d59b85ed1f57bf80e84d6cb0d6d722a2d12ae4c0-1600x745.png" alt="" /><h3>RAG로 검색</h3><p>검색을 수행하기 위해 <strong>ElasticsearchStore</strong> 클라이언트를 구성하여 <strong>index_name</strong> 및 <strong>es_url</strong> 필드를 Elasticsearch URL로 설정합니다. <strong>retrieval_strategy에서</strong> 벡터 검색을 위한 <strong>AsyncDenseVectorStrategy를</strong> 정의했습니다. <strong>AsyncBM25Strategy</strong> (키워드 검색) 및 <strong>AsyncSparseVectorStrategy</strong> (희소 벡터)와 같은 다른 전략도 사용할 수 있습니다. 자세한 내용은 <a href="https://docs.llamaindex.ai/en/stable/api_reference/storage/vector_store/elasticsearch/">공식 문서에서</a> 확인할 수 있습니다.</p>es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200",
   retrieval_strategy=AsyncDenseVectorStrategy(
   )
)<p>다음으로, <strong>VectorStoreIndex</strong> 객체가 생성되며, 여기서 ElasticsearchStore 객체를 사용하여 <strong>vector_store를</strong> 구성합니다. <strong>as_retriever</strong> 메서드를 사용하면 <strong>유사성_top_k</strong> 매개변수를 통해 반환되는 결과의 수를 5로 설정하여 쿼리와 가장 관련성이 높은 문서를 검색합니다.</p>   index = VectorStoreIndex.from_vector_store(vector_store=es)
   retriever = index.as_retriever(similarity_top_k=5)
   results = retriever.retrieve(query)<p>다음 단계는 RAG입니다. 벡터 검색 결과는 LLM을 위한 형식화된 프롬프트에 통합되어 검색된 정보에 따라 상황에 맞는 응답이 가능합니다.</p><p>프롬프트 템플릿에서는 프롬프트 형식을 정의하며, 여기에는 다음이 포함됩니다:</p><ul><li><p>컨텍스트 ({context_str}): 검색기가 검색한 문서입니다.</p></li><li><p>쿼리 ({query_str}): 사용자의 질문입니다.</p></li><li><p>지침: 모델이 외부 지식에 의존하지 않고 상황에 따라 대응할 수 있도록 하는 지침입니다.</p></li></ul>qa_prompt = PromptTemplate(
   "You are a helpful and knowledgeable assistant."
   "Your task is to answer the user's query based solely on the context provided below."
   "Do not use any prior knowledge or external information.\n"
   "---------------------\n"
   "Context:\n"
   "{context_str}\n"
   "---------------------\n"
   "Query: {query_str}\n"
   "Instructions:\n"
   "1. Carefully read and understand the context provided.\n"
   "2. If the context contains enough information to answer the query, provide a clear and concise answer.\n"
   "3. Do not make up or guess any information.\n"
   "Answer: "
)<p>마지막으로 LLM이 프롬프트를 처리하고 상황에 맞는 정확한 응답을 반환합니다.</p>llm = OpenAI(model="gpt-4o")
context_str = "\n\n".join([n.node.get_content() for n in results])
response = llm.complete(
   qa_prompt.format(context_str=context_str, query_str=query)
)

print("Answer:")
print(response)<p>전체 코드는 아래와 같습니다:</p>es = ElasticsearchStore(
   index_name="faq",
   es_url="http://localhost:9200",
   retrieval_strategy=AsyncDenseVectorStrategy(
   )
)


def print_results(results):
   for rank, result in enumerate(results, start=1):
       title = result.metadata.get("title")
       score = result.get_score()
       text = result.get_text()
       print(f"{rank}. title={title} \nscore={score} \ncontent={text}")


def search(query: str):
   index = VectorStoreIndex.from_vector_store(vector_store=es)

   retriever = index.as_retriever(similarity_top_k=10)
   results = retriever.retrieve(QueryBundle(query_str=query))
   print_results(results)

   qa_prompt = PromptTemplate(
       "You are a helpful and knowledgeable assistant."
       "Your task is to answer the user's query based solely on the context provided below."
       "Do not use any prior knowledge or external information.\n"
       "---------------------\n"
       "Context:\n"
       "{context_str}\n"
       "---------------------\n"
       "Query: {query_str}\n"
       "Instructions:\n"
       "1. Carefully read and understand the context provided.\n"
       "2. If the context contains enough information to answer the query, provide a clear and concise answer.\n"
       "3. Do not make up or guess any information.\n"
       "Answer: "
   )

   llm = OpenAI(model="gpt-4o")
   context_str = "\n\n".join([n.node.get_content() for n in results])
   response = llm.complete(
       qa_prompt.format(context_str=context_str, query_str=query)
   )

   print("Answer:")
   print(response)


question = "Elastic services are free?"
print(f"Question: {question}")
search(question)<p>이제 검색을 수행할 수 있습니다(예: "Elastic 서비스가 무료인가요?" ) 그리고 FAQ 데이터 자체를 기반으로 상황에 맞는 답변을 얻을 수 있습니다.</p>Question: Elastic services are free?
Answer:
Elastic services are not entirely free. However, there is a 14-day free trial available for exploring Elastic solutions. After the trial, access to features and services depends on the subscription level.<p>이 응답을 생성하기 위해 다음 문서가 사용되었습니다:</p>1. title=Can I Try Elasticsearch Service For Free 
score=1.0 
content=Yes, sign up for a 14-day free trial. The trial starts the moment a cluster is created.
During the free trial period get access to a deployment to explore Elastic solutions for Enterprise Search, Observability, Security, or the latest version of the Elastic Stack.

2. title=Do You Offer Elastic S Commercial Products 
score=0.9941274512218439 
content=Yes, all Elasticsearch Service customers have access to basic authentication, role-based access control, and monitoring.
Elasticsearch Service Gold, Platinum and Enterprise customers get complete access to all the capabilities in X-Pack: Security, Alerting, Monitoring, Reporting, Graph Analysis &amp; Visualization. Contact us to learn more.

3. title=What Is Elasticsearch Service 
score=0.9896776845746571 
content=Elasticsearch Service is hosted and managed Elasticsearch and Kibana brought to you by the creators of Elasticsearch. Elasticsearch Service is part of Elastic Cloud and ships with features that you can only get from the company behind Elasticsearch, Kibana, Beats, and Logstash. Elasticsearch is a full text search engine that suits a range of uses, from search on websites to big data analytics and more.

4. title=Can I Run The Full Elastic Stack In Elasticsearch Service 
score=0.9880631561979476 
content=Many of the products that are part of the Elastic Stack are readily available in Elasticsearch Service, including Elasticsearch, Kibana, plugins, and features such as monitoring and security. Use other Elastic Stack products directly with Elasticsearch Service. For example, both Logstash and Beats can send their data to Elasticsearch Service. What is run is determined by the subscription level.

5. title=What Is The Difference Between Elasticsearch Service And The Amazon Elasticsearch Service 
score=0.9835054890793161 
content=Elasticsearch Service is the only hosted and managed Elasticsearch service built, managed, and supported by the company behind Elasticsearch, Kibana, Beats, and Logstash. With Elasticsearch Service, you always get the latest versions of the software. Our service is built on best practices and years of experience hosting and managing thousands of Elasticsearch clusters in the Cloud and on premise. For more information, check the following Amazon and Elastic Elasticsearch Service comparison page.
Please note that there is no formal partnership between Elastic and Amazon Web Services (AWS), and Elastic does not provide any support on the AWS Elasticsearch Service.<h2>결론</h2><p>LlamaIndex를 사용해 벡터 데이터베이스로서 Elasticsearch를 지원하는 효율적인 FAQ 검색 시스템을 만드는 방법을 시연했습니다. 임베딩을 사용해 문서를 수집하고 색인을 생성하여 벡터 검색을 가능하게 합니다. 프롬프트 템플릿을 통해 검색 결과가 컨텍스트에 통합되어 LLM으로 전송되면 검색된 문서를 기반으로 정확한 문맥에 맞는 응답을 생성합니다.</p><p>이 워크플로는 정보 검색과 상황에 맞는 응답 생성을 통합하여 정확하고 관련성 높은 결과를 제공합니다.</p><h2>참고 자료</h2><p><a href="https://www.elastic.co/guide/en/cloud/current/ec-faq-getting-started.html">https://www.elastic.co/guide/en/cloud/current/ec-faq-getting-started.html</a></p><p><a href="https://docs.llamaindex.ai/en/stable/api_reference/readers/elasticsearch/">https://docs.llamaindex.ai/en/stable/api_reference/readers/elasticsearch/</a></p><p><a href="https://docs.llamaindex.ai/en/stable/module_guides/indexing/vector_store_index/">https://docs.llamaindex.ai/en/stable/module_guides/indexing/vector_store_index/</a></p><p><a href="https://docs.llamaindex.ai/en/stable/examples/query_engine/custom_query_engine/">https://docs.llamaindex.ai/en/stable/examples/query_engine/custom_query_engine/</a></p><p><a href="https://www.elastic.co/search-labs/integrations/llama-index">https://www.elastic.co/search-labs/integrations/llama-index</a></p><p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-llamaindex-ingest-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-llamaindex-ingest-data</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9f88afa92e90390/6a17f4d33e03d7987d4f2dd0/b8b760bfd8694df43fd74ba90ae5fc1edbe4ce76-1150x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 28 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Apache Airflow를 통해 Elasticsearch로 데이터를 수집하는 방법]]></title>
    <description><![CDATA[Apache Airflow를 통해 Elasticsearch로 데이터를 수집하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>아파치 에어플로우란 무엇인가요?</h2><p>Apache Airflow는 워크플로우를 생성, 예약 및 모니터링하도록 설계된 플랫폼입니다. ETL 프로세스, 데이터 파이프라인 및 기타 복잡한 워크플로를 오케스트레이션하는 데 사용되며 유연성과 확장성을 제공합니다. 시각적 인터페이스와 실시간 모니터링 기능을 통해 파이프라인을 보다 쉽고 효율적으로 관리할 수 있으며, 실행 진행 상황과 결과를 추적할 수 있습니다. 다음은 네 가지 주요 기둥입니다:</p><ul><li><p><strong>동적: </strong>파이프라인은 파이썬으로 정의되어 동적이고 유연한 워크플로를 생성할 수 있습니다.</p></li><li><p><strong>확장성:</strong> 에어플로우를 다양한 환경과 통합할 수 있고, 사용자 지정 오퍼레이터를 생성할 수 있으며, 필요에 따라 특정 코드를 실행할 수 있습니다.</p></li><li><p><strong>우아함:</strong> 파이프라인은 깔끔하고 명시적인 방식으로 작성됩니다.</p></li><li><p><strong>확장성:</strong> 모듈식 아키텍처는 메시지 대기열을 사용하여 임의의 수의 작업자를 조율합니다.</p></li></ul><p>실제로 Airflow는 다음과 같은 시나리오에서 사용할 수 있습니다:</p><ul><li><p><strong>데이터 가져오기: </strong>Elasticsearch와 같은 데이터베이스로의 일일 데이터 수집을 오케스트레이션하세요.</p></li><li><p><strong>로그 모니터링:</strong> 로그 파일의 수집과 처리를 관리한 다음 Elasticsearch에서 분석하여 오류나 이상 징후를 식별합니다.</p></li><li><p><strong>여러 데이터 소스 통합:</strong> 서로 다른 시스템(API, 데이터베이스, 파일)의 정보를 Elasticsearch의 단일 레이어로 결합하여 검색과 보고를 간소화하세요.</p></li></ul><h2>공기 흐름에서 DAG(방향성 비순환 그래프) 이해하기</h2><p>에어플로우에서 워크플로는 DAG(방향성 비순환 그래프)로 표현됩니다. DAG는 작업이 실행되는 순서를 정의하는 구조입니다. DAG의 주요 특징은 다음과 같습니다:</p><ul><li><p><strong>독립적인 작업별 구성:</strong> 각 작업은 작업의 단위를 나타내며 독립적으로 실행되도록 설계되었습니다.</p></li><li><p><strong>시퀀싱: </strong>작업이 실행되는 순서는 DAG에 명시적으로 정의되어 있습니다.</p></li><li><p><strong>재사용성:</strong> DAG는 반복적으로 실행되도록 설계되어 프로세스 자동화를 용이하게 합니다.</p></li></ul><h2>공기 흐름 구성 요소</h2><p>Airflow 에코시스템은 작업을 조율하기 위해 함께 작동하는 여러 구성 요소로 구성되어 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25a23489b8725b3e/6a17dfcfe8fbceba103a1846/bacd83aff625026d62f023e0434baa5782a2761a-1046x628.png" alt="공기 흐름 주요 구성 요소" /><ul><li><p><strong>스케줄러:</strong> 스케줄러: DAG를 예약하고 작업자가 실행할 작업을 보내는 역할을 담당합니다.</p></li><li><p><strong>실행자:</strong> 작업 실행을 관리하여 작업자에게 위임합니다.</p></li><li><p><strong>웹 서버:</strong> DAG 및 작업과 상호 작용하기 위한 그래픽 인터페이스를 제공합니다.</p></li><li><p><strong>Dags 폴더:</strong> 파이썬으로 작성된 DAG를 저장하는 폴더입니다.</p></li><li><p><strong>메타데이터:</strong> 스케줄러와 실행자가 실행 상태를 저장하는 데 사용하는 도구의 저장소 역할을 하는 데이터베이스입니다.</p></li></ul><h2>아파치 에어플로우와 Elasticsearch</h2><p>Apache Airflow와 Elasticsearch를 사용하여 Elasticsearch에서 작업과 색인 결과를 오케스트레이션하는 방법을 시연합니다. 이 데모의 목표는 Elasticsearch 인덱스의 레코드를 업데이트하는 작업 파이프라인을 생성하는 것입니다. 이 인덱스에는 사용자가 평점을 매기고 등급을 지정할 수 있는 영화 데이터베이스가 포함되어 있습니다. 매일 수백 개의 등급이 있는 시나리오를 상상해 보면 등급 기록을 계속 업데이트해야 합니다. 이를 위해 새로운 통합 등급을 검색하고 인덱스의 기록을 업데이트하는 DAG가 매일 실행되도록 개발될 것입니다.</p><p>DAG 흐름에는 등급을 가져오는 작업과 결과를 검증하는 작업이 있습니다. 데이터가 존재하지 않으면 DAG는 실패 작업으로 이동합니다. 그렇지 않으면 데이터가 Elasticsearch에서 색인됩니다. 점수 계산을 담당하는 메커니즘이 있는 메서드를 통해 등급을 검색하여 인덱스의 영화 등급 필드를 업데이트하는 것이 목표입니다.</p><h2>Docker와 함께 Apache Airflow 및 Elasticsearch 사용</h2><p>컨테이너화된 환경을 만들기 위해 Docker와 함께 Apache Airflow를 사용하겠습니다. <a href="https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html">"도커에서 에어플로우 실행하기"</a> 가이드의 지침에 따라 에어플로우를 실제로 설정하세요.</p><p>Elasticsearch의 경우 Elastic Cloud의 클러스터를 사용하겠지만, 원하는 경우 Docker로 Elasticsearch를 구성할 수도 있습니다. 이미 영화 카탈로그가 포함된 인덱스가 생성되어 영화 데이터가 색인화되었습니다. 이러한 영화의 '등급' 필드가 업데이트됩니다.</p><h2>DAG 생성</h2><p>Docker를 통해 설치하면 Airflow가 인식할 수 있도록 DAG 파일을 배치해야 하는 dags 폴더를 포함한 폴더 구조가 생성됩니다.</p><p>그 전에 필요한 종속성이 설치되어 있는지 확인해야 합니다. 이 프로젝트의 종속성은 다음과 같습니다:</p>pip install apache-airflow apache-airflow-providers-elasticsearch<p><code>update_ratings_movies.py</code> 파일을 만들고 작업 코딩을 시작합니다.</p><p>이제 필요한 라이브러리를 가져와 보겠습니다:</p>from airflow import DAG
from airflow.operators.python import PythonOperator, BranchPythonOperator
from airflow.providers.elasticsearch.hooks.elasticsearch import ElasticsearchPythonHook<p>연결과 외부 API 사용을 추상화하여 Airflow와 Elasticsearch 클러스터 간의 통합을 간소화하는 구성 요소인 <a href="https://airflow.apache.org/docs/apache-airflow-providers-elasticsearch/stable/hooks/elasticsearch_python_hook.html"><strong>ElasticsearchPythonHook을</strong></a> 사용하겠습니다.</p><p>다음으로, 주요 인수를 지정하여 DAG를 정의합니다:</p><ul><li><p><strong><code>dag_id</code></strong>DAG의 이름입니다.</p></li><li><p><strong><code>start_date</code></strong>DAG가 시작되는 시기입니다.</p></li><li><p><strong><code>schedule</code></strong>: 주기를 정의합니다(이 경우 매일).</p></li><li><p><strong><code>doc_md</code></strong>문서를 가져와 에어플로우 인터페이스에 표시할 수 있습니다.</p></li></ul><h2>작업 정의하기</h2><p>이제 DAG의 작업을 정의해 보겠습니다. 첫 번째 작업은 영화 등급 데이터를 검색하는 작업을 담당합니다. <code>task_id</code> 을 <code>'get_movie_ratings'</code> 으로 설정한 <strong>파이썬 오퍼레이터를</strong> 사용하겠습니다. <code>python_callable</code> 매개변수는 평점 가져오기를 담당하는 함수를 호출합니다.</p>get_ratings_operator = PythonOperator(
   task_id='get_movie_ratings',
   python_callable=get_movie_ratings_task
)<p>다음으로 결과가 유효한지 검증해야 합니다. 이를 위해 <strong>BranchPythonOperator와</strong> 함께 조건문을 사용하겠습니다. <code>task_id</code> 은 <code>'validate_result'</code> 이 되고 <code>python_callable</code> 은 유효성 검사 함수를 호출합니다. <code>op_args</code> 매개 변수는 이전 작업의 결과인 <code>'get_movie_ratings'</code> 을 유효성 검사 함수에 전달하는 데 사용됩니다.</p>validate_result = BranchPythonOperator(
   task_id='validate_result',
   python_callable=validate_result,
   op_args=["{{ task_instance.xcom_pull(task_ids='get_movie_ratings') }}"]
)<p>유효성 검사가 성공하면 <code>'get_movie_ratings'</code> 작업에서 데이터를 가져와서 Elasticsearch로 색인합니다. 이를 위해 새 작업인 <code>'index_movie_ratings'</code> 을 생성하여 <strong>PythonOperator를</strong> 사용합니다. <code>op_args</code> 매개변수는 <code>'get_movie_ratings'</code> 작업의 결과를 인덱싱 함수에 전달합니다.</p>index_ratings_operator = PythonOperator(
   task_id='index_movie_ratings',
   python_callable=index_movie_ratings_task,
   op_args=["{{ task_instance.xcom_pull(task_ids='get_movie_ratings') }}"]
)<p>유효성 검사 결과 실패로 표시되면 DAG는 실패 알림 작업으로 진행합니다. 이 예에서는 단순히 메시지를 인쇄하지만 실제 시나리오에서는 실패에 대해 알리도록 알림을 구성할 수 있습니다.</p>failed_get_rating_operator = PythonOperator(
   task_id='failed_get_rating_operator',
   python_callable=lambda: print('Ratings were False, skipping indexing.')
)<p>마지막으로 작업 종속성을 정의하여 올바른 순서로 실행되도록 합니다:</p>get_ratings_operator &gt;&gt; validate_result &gt;&gt; [index_ratings_operator, failed_get_rating_operator]<p>이제 DAG의 전체 코드를 따르세요:</p>"""
DAG update Rating Movies
"""
import ast
import random

from airflow import DAG
from datetime import datetime

from airflow.operators.python import PythonOperator, BranchPythonOperator
from airflow.providers.elasticsearch.hooks.elasticsearch import ElasticsearchPythonHook


def index_movie_ratings_task(movies):
   es_hook = ElasticsearchPythonHook(hosts=None,
                                     es_conn_args={
                                         "cloud_id": "cloud_id"
                                         "api_key": "api-key"
                                     })
   es_client = es_hook.get_conn
   actions = []
   for movie in ast.literal_eval(movies):
       actions.append(
           {
               "update": {
                   "_id": movie["id"],
                   "_index": "movies"
               }
           }
       )
       actions.append(
           {
               "doc": {
                   "rating": movie["rating"]
               },
               "doc_as_upsert": True
           }
       )
   result = es_client.bulk(operations=actions)
   print(f"Ingestion completed.")
   print(result)
   return True


def get_movie_ratings_task():
   movies = [
       {"id": i, "rating": round(random.uniform(1, 10), 1)}
       for i in range(1, 100)
   ]
   return movies

def validate_result(result):
   if not result:
       return 'failed_get_rating_operator'
   else:
       return 'index_movie_ratings'


with DAG(
       dag_id="update_ratings_movies_2024",
       start_date=datetime(2024, 12, 29),
       schedule="@daily",
       doc_md=__doc__,
):
   get_ratings_operator = PythonOperator(
       task_id='get_movie_ratings',
       python_callable=get_movie_ratings_task
   )

   validate_result = BranchPythonOperator(
       task_id='validate_result',
       python_callable=validate_result,
       op_args=["{{ task_instance.xcom_pull(task_ids='get_movie_ratings') }}"],
       provide_context=True
   )

   index_ratings_operator = PythonOperator(
       task_id='index_movie_ratings',
       python_callable=index_movie_ratings_task,
       op_args=["{{ task_instance.xcom_pull(task_ids='get_movie_ratings') }}"]
   )

   failed_get_rating_operator = PythonOperator(
       task_id='failed_get_rating_operator',
       python_callable=lambda: print('Ratings were False, skipping indexing.')
   )

get_ratings_operator &gt;&gt; validate_result &gt;&gt; [index_ratings_operator, failed_get_rating_operator]<h2>DAG 실행 시각화</h2><p>Apache Airflow 인터페이스에서 DAG의 실행을 시각화할 수 있습니다. "DAG" 탭으로 이동하여 생성한 DAG를 찾으면 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9c5de210a5a264ad/6a17dfd00b0bed0290dd34cf/905b9191c4e3191e8b5608174d4c555370bf25eb-1600x760.png" alt="Elasticsearch를 사용하여 Apache Airflow 인터페이스에서 DAG 실행 시각화하기" /><p>아래에서 작업의 실행과 각 상태를 시각화하여 확인할 수 있습니다. 특정 날짜의 실행을 선택하면 각 작업의 로그에 액세스할 수 있습니다. <strong><code>index_movie_ratings</code></strong> 작업에서 인덱스에서 인덱싱 결과를 확인할 수 있으며, 성공적으로 완료되었음을 알 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0beddf311a808d24/6a17dfd2033c8d76de6bb0ba/73c3f738d27500cf153377bedf1aa2b67a94a8c8-1600x648.png" alt="Elasticsearch를 사용하여 Apache Airflow에서 작업 실행과 그 상태를 시각화합니다." /><p>다른 탭에서는 작업 및 DAG에 대한 추가 정보에 액세스하여 잠재적인 문제를 분석하고 해결하는 데 도움을 받을 수 있습니다.</p><h2>결론</h2><p>이 문서에서는 Apache Airflow와 Elasticsearch를 통합하여 데이터 수집 솔루션을 만드는 방법을 보여드렸습니다. DAG를 구성하고, 동영상 데이터 검색, 유효성 검사, 인덱싱을 담당하는 작업을 정의하고, Airflow 인터페이스에서 이러한 작업의 실행을 모니터링하고 시각화하는 방법을 보여드렸습니다.</p><p>이 접근 방식은 다양한 유형의 데이터와 워크플로에 쉽게 적용할 수 있으므로 Airflow는 다양한 시나리오에서 데이터 파이프라인을 오케스트레이션하는 데 유용한 도구입니다.</p><h2>참고 자료</h2><p>Apache AirFlow</p><p><a href="https://airflow.apache.org/">https://airflow.apache.org/</a></p><p>Docker로 Apache Airflow 설치</p><p><a href="https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html">https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html</a></p><p>Elasticsearch 파이썬 훅</p><p><a href="https://airflow.apache.org/docs/apache-airflow-providers-elasticsearch/stable/hooks/elasticsearch_python_hook.html">https://airflow.apache.org/docs/apache-airflow-providers-elasticsearch/stable/hooks/elasticsearch_python_hook.html</a></p><p>파이썬 연산자</p><p><a href="https://airflow.apache.org/docs/apache-airflow/stable/howto/operator/python.html">https://airflow.apache.org/docs/apache-airflow/stable/howto/operator/python.html</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/apache-airflow-elasticsearch-ingest-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/apache-airflow-elasticsearch-ingest-data</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28475ace97d1d989/6a1704c9286714d6dd93e1ec/5d4b47ac5d2ba453fc19dcc15efa2aed5f55d88b-1440x1355.png" length="0" type="image/png"/>
    <pubDate>Fri, 17 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kafka를 통해 Elasticsearch로 데이터를 수집하는 방법]]></title>
    <description><![CDATA[Python, Docker Compose, Kafka Connect를 사용해 효율적인 데이터 수집, 색인, 시각화를 위해 Apache Kafka와 Elasticsearch를 통합하는 단계별 안내서입니다.]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 데이터 수집 및 색인을 위해 Apache Kafka와 Elasticsearch를 통합하는 방법을 보여드립니다. Kafka의 개요와 생산자 및 소비자 개념에 대해 설명하고, Apache Kafka를 통해 메시지를 수신하고 색인할 로그 인덱스를 생성합니다. 이 프로젝트는 Python으로 구현되었으며, 코드는 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-through-apache-kafka">GitHub에서</a> 확인할 수 있습니다.</p><h3><strong>필수 구성 요소</strong></h3><ul><li><p>도커 및 도커 컴포즈: 컴퓨터에 도커 및 도커 컴포즈가 설치되어 있는지 확인합니다.</p></li><li><p>Python 3.x: 생산자 및 소비자 스크립트를 실행합니다.</p></li></ul><h3><strong>아파치 카프카 소개</strong></h3><p>Apache Kafka는 높은 확장성과 가용성, 내결함성을 지원하는 분산형 스트리밍 플랫폼입니다. Kafka에서는 주요 구성 요소를 통해 데이터 관리가 이루어집니다:</p><ul><li><p><strong>브로커</strong>: 생산자와 소비자 간의 메시지 저장 및 배포를 담당합니다.</p></li><li><p><strong>주키퍼</strong>: 클러스터의 상태, 파티션 리더, 소비자 정보를 제어하여 카프카 브로커를 관리하고 조정합니다.</p></li><li><p><strong>주제</strong>: 데이터가 게시되고 소비를 위해 저장되는 채널입니다.</p></li><li><p><strong>소비자와 생산자</strong>: 생산자가 토픽에 데이터를 전송하는 동안 소비자는 해당 데이터를 검색합니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4aae32304d7417f6/6a17f7be577262b47c1bcdac/89a37243baec48bbdfa85e3298fc91082322ed4e-1600x868.png" alt="다이어그램 아파치 카프카" /><p>이러한 구성 요소는 함께 작동하여 데이터 스트리밍을 위한 강력한 프레임워크를 제공하는 카프카 생태계를 형성합니다.</p><h3><strong>프로젝트 구조</strong></h3><p>데이터 수집 프로세스를 이해하기 위해 데이터 수집 프로세스를 여러 단계로 나누었습니다:</p><ul><li><p><strong>인프라 프로비저닝</strong>: Kafka, Elasticsearch, Kibana를 지원하기 위한 Docker 환경 설정.</p></li><li><p><strong>프로듀서 만들기</strong>: 로그 주제로 데이터를 전송하는 Kafka 프로듀서 구현하기.</p></li><li><p><strong>소비자 생성</strong>: Elasticsearch에서 메시지를 읽고 색인하기 위한 Kafka 소비자 개발.</p></li><li><p><strong>수집 유효성</strong> 검사: 전송 및 소비된 데이터를 확인하고 유효성을 검사합니다.</p></li></ul><h3><strong>Docker Compose를 사용한 인프라 구성</strong></h3><p>필요한 서비스를 구성하고 관리하기 위해 Docker Compose를 활용했습니다. 아래에는 Apache Kafka, Elasticsearch, Kibana의 통합에 필요한 각 서비스를 설정하여 데이터 수집 프로세스를 보장하는 Docker Compose 코드가 나와 있습니다.</p>version: "3"

services:

  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181

  kafka:
    image: confluentinc/cp-kafka:latest
    container_name: kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
      - "9094:9094"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST:${HOST_IP}:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.1
    container_name: elasticsearch-8.15.1
    environment:
      - node.name=elasticsearch
      - xpack.security.enabled=false
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - ./elasticsearch:/usr/share/elasticsearch/data
    ports:
      - 9200:9200

  kibana:
    image: docker.elastic.co/kibana/kibana:8.15.1
    container_name: kibana-8.15.1
    ports:
      - 5601:5601
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
      ELASTICSEARCH_HOSTS: '["http://elasticsearch:9200"]'<p>Elasticsearch Labs <a href="https://github.com/andreluiz1987/elasticsearch-labs/tree/supporting-blog/elasticsearch-apache-kafka/supporting-blog-content/elasticsearch-through-apache-kafka">GitHub</a> 리포지토리에서 직접 파일에 액세스할 수 있습니다.</p><h3><strong>카프카 프로듀서를 사용한 데이터 전송</strong></h3><p>프로듀서는 로그 주제에 메시지를 보낼 책임이 있습니다. 메시지를 일괄적으로 전송함으로써 네트워크 사용 효율을 높이고 일괄 전송의 양과 지연 시간을 각각 제어하는 <code>batch_size</code> 및 <code>linger_ms</code> 설정으로 최적화할 수 있습니다. <code>acks='all'</code> 구성은 중요한 로그 데이터에 필수적인 메시지를 영구적으로 저장할 수 있도록 합니다.</p>producer = KafkaProducer(
   bootstrap_servers=['localhost:9092'],  # Specifies the Kafka server to connect
   value_serializer=lambda x: json.dumps(x).encode('utf-8'),  # Serializes data as JSON and encodes it to UTF-8 before sending
   batch_size=16384,     # Sets the maximum batch size in bytes (here, 16 KB) for buffered messages before sending
   linger_ms=10,         # Sets the maximum delay (in milliseconds) before sending the batch
   acks='all'            # Specifies acknowledgment level; 'all' ensures message durability by waiting for all replicas to acknowledge
)


def generate_log_message():
   levels = ["INFO", "WARNING", "ERROR", "DEBUG"]
   messages = [
       "User login successful",
       "User login failed",
       "Database connection established",
       "Database connection failed",
       "Service started",
       "Service stopped",
       "Payment processed",
       "Payment failed"
   ]
   log_entry = {
       "level": random.choice(levels),
       "message": random.choice(messages),
       "timestamp": time.time()
   }
   return log_entry

def send_log_batches(topic, num_batches=5, batch_size=10):
   for i in range(num_batches):
       logger.info(f"Sending batch {i + 1}/{num_batches}")
       for  in range(batch_size):
           log_message = generate_log_message()
           producer.send(topic, value=log_message)
       producer.flush()


if __name__ == "__main__":
   topic = "logs"
   send_log_batches(topic)
   producer.close()<p>프로듀서를 시작할 때 아래와 같이 메시지가 토픽에 일괄적으로 전송됩니다:</p>INFO:kafka.conn:Set configuration …
INFO:log_producer:Sending batch 1/5 
INFO:log_producer:Sending batch 2/5
INFO:log_producer:Sending batch 3/5
INFO:log_producer:Sending batch 4/5<h3><strong>Kafka Consumer를 통한 데이터 소비 및 색인화</strong></h3><p>소비자는 메시지를 효율적으로 처리하도록 설계되어 로그 주제에서 배치를 소비하고 Elasticsearch로 색인합니다. <code>auto_offset_reset='latest'</code> 을 사용하면 소비자가 이전 메시지를 무시하고 가장 최근 메시지부터 처리를 시작하고 <code>max_poll_records=10</code> 은 일괄 처리를 10개의 메시지로 제한합니다. <code>fetch_max_wait_ms=2000</code> 을 사용하면 소비자는 배치 처리 전에 충분한 메시지가 누적될 때까지 최대 2초 동안 기다립니다.</p><p>메인 루프에서 소비자는 로그 메시지를 소비하고, 처리하고, 각 배치를 Elasticsearch로 색인하여 지속적인 데이터 수집을 보장합니다.</p>consumer = KafkaConsumer(
   'logs',                               
   bootstrap_servers=['localhost:9092'],
   auto_offset_reset='latest',            # Ensures reading from the latest offset if the group has no offset stored
   enable_auto_commit=True,               # Automatically commits the offset after processing
   group_id='log_consumer_group',         # Specifies the consumer group to manage offset tracking
   max_poll_records=10,                   # Maximum number of messages per batch
   fetch_max_wait_ms=2000                 # Maximum wait time to form a batch (in ms)
)

def create_bulk_actions(logs):
   for log in logs:
       yield {
           "_index": "logs",
           "_source": {
               'level': log['level'],
               'message': log['message'],
               'timestamp': log['timestamp']
           }
       }

if __name__ == "__main__":
   try:
       print("Starting message processing…")
       while True:

           messages = consumer.poll(timeout_ms=1000)  # Poll receive messages

           # process each batch messages
           for _, records in messages.items():
               logs = [json.loads(record.value) for record in records]
               bulk_actions = create_bulk_actions(logs)
               response = helpers.bulk(es, bulk_actions)
               print(f"Indexed {response[0]} logs.")
   except Exception as e:
       print(f"Erro: {e}")
   finally:
       consumer.close()
       print(f"Finish")<h3><strong>Kibana에서 데이터 시각화하기</strong></h3><p>Kibana를 사용하면 Kafka에서 수집되어 Elasticsearch에서 색인된 데이터를 탐색하고 검증할 수 있습니다. Kibana의 <strong>개발 도구에</strong> 액세스하여 색인된 메시지를 보고 데이터가 예상대로인지 확인할 수 있습니다. 예를 들어, 카프카 프로듀서가 각각 10개의 메시지를 5개의 배치로 전송했다면 인덱스에 총 50개의 레코드가 표시되어야 합니다.</p><p>데이터를 확인하려면 <strong>개발 도구</strong> 섹션에서 다음 쿼리를 사용할 수 있습니다:</p>GET /logs/_search
{
  "query": {
    "match_all": {}
  }
}<p>대응:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc15fb278fe6f984e/6a17f7c0b1e1131ac279f404/f44f95fc27bba50991412d5c7e7728519b9bdec4-688x1024.png" alt="응답 데이터 확인 - Kafka &amp; Elasticsearch​" /><p>또한, Kibana는 분석을 보다 직관적이고 대화형으로 만드는 데 도움이 되는 시각화 및 대시보드를 생성하는 기능을 제공합니다. 아래에서 처리된 정보에 대한 이해를 높이기 위해 다양한 형식으로 데이터를 보여주는 대시보드와 시각화의 몇 가지 예를 볼 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltabc2856c9feedc47/6a17f7c13e9e4522bfba1651/a18e0ebb543e929136d4786651bc6cee32fa69bc-1600x470.png" alt="Kibana 시각화 - Kafka &amp; Elasticsearch​" /><h3><strong>Kafka Connect를 통한 데이터 수집</strong></h3><p>Kafka Connect는 데이터베이스나 파일 시스템과 같은 데이터 소스와 대상(싱크) 간의 통합을 용이하게 하도록 설계된 서비스입니다. 데이터 이동을 자동으로 처리하는 사전 정의된 커넥터로 작동합니다. 저희의 경우, Elasticsearch는 데이터 싱크 역할을 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53e3acaf62e7dbed/6a17f7c3577262c5151bcdb0/52a6982c864fdc04cb7a8a5fb02e67dca0ba8226-1600x819.png" alt="Kafka Connect를 통한 데이터 수집" /><p>Kafka Connect를 사용하면 데이터 수집 프로세스를 간소화할 수 있으므로 데이터 수집 워크플로우를 Elasticsearch에 수동으로 구현할 필요가 없습니다. 적절한 커넥터를 사용하면 최소한의 설정과 추가 코딩 없이도 Kafka Connect를 통해 Kafka 토픽으로 전송된 데이터를 Elasticsearch에서 직접 색인할 수 있습니다.</p><h4><strong>카프카 커넥트로 작업하기</strong></h4><p>Kafka Connect를 구현하기 위해 Docker Compose 설정에<a href="https://github.com/andreluiz1987/es-apache-kafka/blob/main/docker-compose.yml#L31"> kafka-connect 서비스를 </a>추가합니다. 이 구성의 핵심은 데이터 인덱싱을 처리할 Elasticsearch 커넥터를 설치하는 것입니다.</p><p>서비스를 구성하고 Kafka Connect 컨테이너를 생성한 후에는 Elasticsearch 커넥터를 위한 구성 파일이 필요합니다. 이 파일에는 다음과 같은 필수 매개변수가 정의되어 있습니다:</p><ul><li><p><code>connection.url</code>: Elasticsearch용 연결 URL입니다.</p></li><li><p><code>topics</code>: 커넥터가 모니터링할 카프카 토픽(이 경우 "로그").</p></li><li><p><code>type.name</code>: Elasticsearch의 문서 유형(일반적으로 _doc).</p></li><li><p><code>value.converter</code>: Kafka 메시지를 JSON 형식으로 변환합니다.</p></li><li><p><code>value.converter.schemas.enable</code>: 스키마를 포함할지 여부를 지정합니다.</p></li><li><p><code>schema.ignore</code> 및 <code>key.ignore</code>: 인덱싱 중 Kafka 스키마 및 키를 무시하도록 설정합니다.</p></li></ul><p>아래는 <code>curl</code> 명령어로 Kafka Connect에서 Elasticsearch 커넥터를 생성하는 방법입니다:</p>curl --location '{{url}}/connectors' \
--header 'Content-Type: application/json' \
--data '{
    "name": "elasticsearch-sink-connector",
    "config": {
        "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
        "topics": "logs",
        "connection.url": "http://elasticsearch:9200",
        "type.name": "_doc",
        "value.converter": "org.apache.kafka.connect.json.JsonConverter",
        "value.converter.schemas.enable": "false",
        "schema.ignore": "true",
        "key.ignore": "true"
    }
}'<p>이 구성을 사용하면 Kafka Connect는 "logs" 항목으로 전송된 데이터를 자동으로 수집하고 Elasticsearch에서 색인하기 시작합니다. 이 접근 방식을 사용하면 추가 코딩 없이도 완전히 자동화된 데이터 수집 및 인덱싱이 가능하므로 전체 통합 프로세스를 간소화할 수 있습니다.</p><h3><strong>결론</strong></h3><p>Kafka와 Elasticsearch를 통합하면 실시간 데이터 수집과 분석을 위한 강력한 파이프라인이 만들어집니다. 이 가이드는 향후 더 복잡한 요구 사항에 적응할 수 있도록 Kibana에서 원활한 시각화 및 분석을 통해 강력한 데이터 수집 아키텍처를 구축하기 위한 기초적인 접근 방식을 제공합니다.</p><p>또한, Kafka Connect를 사용하면 데이터를 처리하고 색인하기 위한 추가 코드가 필요 없기 때문에 Kafka와 Elasticsearch 간의 통합이 훨씬 더 간소화됩니다. Kafka Connect를 사용하면 최소한의 구성으로 특정 토픽으로 전송된 데이터를 Elasticsearch에서 자동으로 색인할 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-apache-kafka-ingest-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-apache-kafka-ingest-data</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53e3acaf62e7dbed/6a17f7c3577262c5151bcdb0/52a6982c864fdc04cb7a8a5fb02e67dca0ba8226-1600x819.png" length="0" type="image/png"/>
    <pubDate>Tue, 24 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Apache Camel을 통해 Elasticsearch로 데이터를 수집하는 방법]]></title>
    <description><![CDATA[실제 예제를 통해 Apache Camel을 통해 Elasticsearch로 데이터를 수집하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Apache Camel을 사용해 Elasticsearch로 데이터를 수집하는 것은 검색 엔진의 견고함과 통합 프레임워크의 유연성을 결합하는 프로세스입니다. 이 글에서는 Apache Camel이 어떻게 데이터 수집을 간소화하고 Elasticsearch로 최적화할 수 있는지 살펴보겠습니다. 이 기능을 설명하기 위해, Apache Camel을 구성하고 사용하여 Elasticsearch로 데이터를 전송하는 방법을 단계별로 보여주는 입문용 애플리케이션을 구현해 보겠습니다.</p><h2>아파치 카멜이란 무엇인가요?</h2><p>Apache Camel은 다양한 시스템 연결을 간소화하는 오픈소스 통합 프레임워크로, 개발자는 시스템 통신의 복잡성에 대한 걱정 없이 비즈니스 로직에 집중할 수 있습니다. Camel의 중심 개념은 "경로(" )로, 메시지가 출발지에서 목적지까지 이동하는 경로를 정의하며 변환, 유효성 검사, 필터링 등의 중간 단계가 포함될 수 있습니다.</p><h3>아파치 카멜 아키텍처</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt05652327efd4d5d1/6a17e6dafbc5f8a588491a8b/bef8145623a8fa80f929f9faa57ce0c460be2d0b-884x458.png" alt="아파치 카멜 아키텍처" /><p>Camel은 "구성 요소" 를 사용하여 데이터베이스 및 메시징 서비스와 같은 다양한 시스템 및 프로토콜에 연결하고 "엔드포인트" 를 사용하여 메시지의 시작 및 종료 지점을 나타냅니다. 이러한 개념은 모듈식의 유연한 설계를 제공하므로 복잡한 통합을 효율적이고 확장 가능하게 구성하고 관리하기가 더 쉬워집니다.</p><h2>Elasticsearch와 Apache Camel 사용</h2><p>Apache Camel을 사용해 데이터를 Elasticsearch 클러스터로 수집하는 간단한 Java 애플리케이션을 구성하는 방법을 보여드리겠습니다. Apache Camel에 정의된 경로를 사용하여 Elasticsearch에서 데이터를 생성, 업데이트 및 삭제하는 프로세스도 다룹니다.</p><h3>1. 종속성 추가하기</h3><p>이 통합을 구성하는 첫 번째 단계는 프로젝트의 <code>pom.xml</code> 파일에 필요한 종속성을 추가하는 것입니다. 여기에는 Apache Camel 및 Elasticsearch 라이브러리가 포함됩니다. 새로운 Java API 클라이언트 라이브러리를 사용할 것이므로 <code>camel-elasticsearch</code> 컴포넌트를 가져와야 하며 버전은 <code>camel-core</code> 라이브러리와 동일해야 합니다.</p><p>Java 로우레벨 Rest 클라이언트를 사용하려면 Elasticsearch 로우레벨 Rest 클라이언트 구성 요소를 사용해야 합니다.</p>&lt;dependency&gt;
   &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
   &lt;artifactId&gt;camel-core&lt;/artifactId&gt;
   &lt;version&gt;4.7.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
   &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
   &lt;artifactId&gt;camel-elasticsearch&lt;/artifactId&gt;
   &lt;version&gt;4.7.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
   &lt;groupId&gt;org.apache.camel&lt;/groupId&gt;
   &lt;artifactId&gt;camel-jackson&lt;/artifactId&gt;
   &lt;version&gt;4.7.0&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
   &lt;groupId&gt;co.elastic.clients&lt;/groupId&gt;
   &lt;artifactId&gt;elasticsearch-java&lt;/artifactId&gt;
   &lt;version&gt;8.14.3&lt;/version&gt;
&lt;/dependency&gt;
<h3>2. Camel 컨텍스트 구성 및 실행</h3><p>구성은 경로를 정의하고 실행하는 기반이 되는 <code>DefaultCamelContext</code> 클래스를 사용하여 새 Camel 컨텍스트를 만드는 것으로 시작됩니다. 다음으로, Apache Camel이 Elasticsearch 클러스터와 상호 작용할 수 있도록 Elasticsearch 구성 요소를 구성합니다. <code>ESlasticsearchComponent</code> 인스턴스는 로컬 Elasticsearch 클러스터의 기본 주소인 <code>localhost:9200</code> 주소에 연결하도록 구성됩니다. 인증이 필요한 환경 설정의 경우 구성 요소를 구성하고 기본 인증을 활성화하는 방법에 대한 설명서( <strong>"구성 요소 구성 및 기본 인증 활성화"</strong> 참조)를 읽어보셔야 합니다.</p>public class ESComponent {

    public static ElasticsearchComponent getInstance() {
        var elasticsearch = new ElasticsearchComponent();
        elasticsearch.setHostAddresses("localhost:9200");
        return elasticsearch;
    }

    public static String getName() {
        return "elasticsearch";
    }
}
<p>그런 다음 이 구성 요소를 Camel 컨텍스트에 추가하여 정의된 경로가 이 구성 요소를 사용하여 Elasticsearch에서 작업을 수행할 수 있도록 합니다.</p>try (var context = new DefaultCamelContext()) {
   context.addComponent(ESComponent.getName(), ESComponent.getInstance());
   context.addRoutes(new OperationBulkRoute());
   context.start();
}
<p>그 후 경로가 컨텍스트에 추가됩니다. 문서 일괄 색인, 업데이트, 삭제를 위한 경로를 생성합니다.</p><h3>3. Camel 경로 구성</h3><h4>데이터 인덱싱</h4><p>첫 번째로 구성할 경로는 데이터 인덱싱을 위한 것입니다. 영화 카탈로그가 포함된 JSON 파일을 사용하겠습니다. 경로는 <a href="https://gist.github.com/andreluiz1987/40756874b5fbea0a29586f9376d7f1f4"><code>src/main/resources/movies.json</code></a> 에 있는 파일을 읽고, JSON 콘텐츠를 Java 객체로 역직렬화한 다음, 여러 메시지를 하나로 결합하는 집계 전략을 적용하여 Elasticsearch에서 일괄 작업을 수행할 수 있도록 구성됩니다. 메시지당 500개 항목의 크기가 구성되었으므로 대량으로 한 번에 500개의 필름을 색인합니다.</p><p>Elasticsearch 작업 일괄 라우팅</p>String URI_BULK_OPERATION = String
       .format("elasticsearch://elasticsearch?operation=%s&amp;indexName=%s",
               IndexOperationConfig.BULK_OPERATION,
               INDEX_NAME);
public class OperationBulkRoute extends RouteBuilder {
   private static final Log log = LogFactory.getLog(OperationBulkRoute.class);
   private static final int BULK_SIZE = 500;

   @Override
   public void configure() {
       from("file:src/main/resources?fileName=movies.json&amp;noop=true")
               .routeId("route-bulk-ingest")
               .unmarshal().json()
               .split(body())
               .aggregate(constant(true), new BulkAggregationStrategy())
               .completionSize(BULK_SIZE)
               .to(URI_BULK_OPERATION)
               .process(exchange -&gt; {
                   var body = exchange.getIn().getBody(String.class);
                   log.info(String.format("Response: %s", body));
               })
               .end();
   }
}
<p>문서 배치가 Elasticsearch의 대량 작업 엔드포인트로 전송됩니다. 이 접근 방식은 대량의 데이터를 처리할 때 효율성과 속도를 보장합니다.</p><h4>데이터 업데이트</h4><p>다음 경로는 문서를 업데이트하는 것입니다. 이전 단계에서 일부 영화를 색인화했으며 이제 참조 코드로 문서를 검색하는 새 경로를 만든 다음 등급 필드를 업데이트합니다.</p><p>Elasticsearch 구성 요소가 등록되고 사용자 정의 경로 IngestionRoute가 추가되는 Camel 컨텍스트 <code>(DefaultCamelContext)</code> 를 설정합니다. 작업은 직접:업데이트-인제스트먼트 엔드포인트에서 경로를 시작하는 ProducerTemplate을 통해 문서 코드를 전송하는 것으로 시작됩니다.</p>try (var context = new DefaultCamelContext()) {
    context.addComponent(ESComponent.getName(), ESComponent.getInstance());
    context.addRoutes(new IngestionRoute());
    context.start();
    ProducerTemplate producerTemplate = context.createProducerTemplate();
    producerTemplate.sendBody("direct:update-ingestion", documentCode);
    Thread.sleep(5000);
}
<p>다음으로, 이 흐름의 입력 엔드포인트인 IngestionRoute가 있습니다. 이 경로는 여러 파이프라인 연산을 수행합니다. 먼저, 코드별로 문서를 찾기 위해 Elasticsearch에서 검색을 수행합니다 <code>(direct:search-by-id)</code>, 여기서 SearchByCodeProcessor는 코드를 기반으로 쿼리를 조합합니다. 그런 다음, 검색된 문서가 UpdateRatingProcessor에 의해 처리되고, 그 결과를 Movie 개체로 변환하고, 영화 등급을 특정 값으로 업데이트하고, 업데이트를 위해 업데이트된 문서를 다시 Elasticsearch로 전송할 수 있도록 준비합니다.</p>public class IngestionRoute extends RouteBuilder {
    private static final Log log = LogFactory.getLog(IngestionRoute.class);

    @Override
    public void configure() throws Exception {

        from("direct:update-ingestion")
                .pipeline()
                .to("direct:search-by-id")
                .to(URI_SEARCH_OPERATION)
                .to("direct:update-rating")
                .to(URI_UPDATE_OPERATION)
                .process(exchange -&gt; {
                    var body = exchange.getIn().getBody(String.class);
                    log.info(String.format("Response: %s", body));
                })
                .end();

        from("direct:search-by-id")
                .process(new SearchByCodeProcessor());

        from("direct:update-rating")
                .process(new UpdateRatingProcessor());
    }
}
<p><code>SearchByCodeProcessor</code> 프로세서는 검색 쿼리를 실행하도록만 구성되었습니다:</p>public class SearchByCodeProcessor implements Processor {
    @Override
    public void process(Exchange exchange) throws Exception {
        var code = exchange.getIn().getBody();

        String query = "{\n" +
                "  \"query\": {\n" +
                "   \"term\": {\n" +
                "     \"code\": {\n" +
                "       \"value\":" + code + "\n" +
                "     }\n" +
                "   }\n" +
                "  }\n" +
                "}";
        exchange.setProperty("document_code", code);
        exchange.getIn().setBody(query);
    }
}
<p><code>UpdateRatingProcessor</code> 프로세서는 등급 필드를 업데이트할 책임이 있습니다.</p>public class UpdateRatingProcessor implements Processor {

    private final ObjectMapper objectMapper;

    public UpdateRatingProcessor() {
        this.objectMapper = new ObjectMapper();
        this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public void process(Exchange exchange) throws Exception {

        HitsMetadata response = exchange.getIn().getBody(HitsMetadata.class);
        var code = Long.parseLong(exchange.getProperty("document_code").toString());

        if (response != null &amp;&amp; response.hits() != null) {

            var documents = parseToMovies(response);

            var optionalMovie = documents.stream()
                    .filter(document -&gt; code == (document.getSource().getCode())).findAny();

            optionalMovie.ifPresent(document -&gt; {
                document.getSource().setRating(13.0);
                Map&lt;String, Object&gt; updateMap = new HashMap&lt;&gt;();
                updateMap.put("doc", document.getSource());
                exchange.getIn().setHeader("indexId", document.getId());
                exchange.getIn().setBody(updateMap);
            });
        }
    }
<h4>데이터 삭제</h4><p>마지막으로 문서 삭제 경로가 구성됩니다. 여기서는 해당 ID를 사용하여 문서를 삭제합니다. Elasticsearch에서 문서를 삭제하려면 문서 식별자, 즉 문서가 저장되어 있는 인덱스를 알고 삭제 요청을 실행해야 합니다. Apache Camel에서는 아래와 같이 새 경로를 생성하여 이 작업을 수행합니다.</p><p>경로는 진입점 역할을 하는 direct:op-delete 엔드포인트에서 시작됩니다. 문서를 삭제해야 하는 경우 해당 문서의 식별자 <code>(_id)</code> 가 메시지 본문으로 수신됩니다. 그런 다음 경로에서는 메시지 본문에서 _id를 추출하는 간단한<code>("${body}")</code> 을 사용하여 이 식별자 값으로 indexId 헤더를 설정합니다.</p>public class OperationDeleteRoute extends RouteBuilder {
   private static final Log log = LogFactory.getLog(OperationDeleteRoute.class);

   @Override
   public void configure() {
       from("direct:op-delete")
               .routeId("route-delete")
               .setHeader("indexId", simple("${body}"))
               .to(URI_DELETE_OPERATION)
               .process(exchange -&gt; {
                   var body = exchange.getIn().getBody(String.class);
                   log.info(String.format("Response: %s", body));
               })
               .end();
       ;
   }
}
String URI_DELETE_OPERATION = String
       .format("elasticsearch://elasticsearch?operation=%s&amp;indexName=%s",
               IndexOperationConfig.DELETE_OPERATION,
               INDEX_NAME);
<p>마지막으로, 메시지는 URI_DELETE_OPERATION에 의해 지정된 엔드포인트로 전달되며, 이 엔드포인트는 해당 인덱스에서 문서 제거 작업을 수행하기 위해 Elasticsearch에 연결됩니다.
이제 경로를 생성했으므로, Elasticsearch 구성 요소를 포함하도록 구성된 Camel 컨텍스트 <code>(DefaultCamelContext)</code> 를 생성할 수 있습니다.</p>try (var context = new DefaultCamelContext()) {
   context.addComponent(ESComponent.getName(), ESComponent.getInstance());
   context.addRoutes(new OperationDeleteRoute());
   context.start();
   ProducerTemplate producerTemplate = context.createProducerTemplate();
   producerTemplate.sendBody("direct:op-delete", documentId);
}
<p>다음으로 <code>OperationDeleteRoute</code> 클래스에 의해 정의된 삭제 경로가 컨텍스트에 추가됩니다. 컨텍스트가 초기화된 상태에서 <code>ProducerTemplate</code> 을 사용하여 삭제해야 하는 문서의 식별자를 <code>direct:op-delete</code> 엔드포인트로 전달하면 삭제 경로가 트리거됩니다.</p><h2>결론</h2><p>Apache Camel과 Elasticsearch의 통합을 통해 강력하고 효율적인 데이터 수집이 가능하며, 색인, 업데이트, 삭제와 같은 다양한 데이터 조작 시나리오를 처리할 수 있는 경로를 정의할 수 있는 Camel의 유연성을 활용할 수 있습니다. 이 설정을 사용하면 복잡한 프로세스를 확장 가능한 방식으로 오케스트레이션하고 자동화하여 Elasticsearch에서 데이터를 효율적으로 관리할 수 있습니다. 이 예는 이러한 도구를 함께 사용하여 데이터 수집을 위한 효율적이고 적응력 있는 솔루션을 만드는 방법을 보여줍니다.</p><h2>참고 자료</h2><ul><li><p><a href="https://camel.apache.org/manual/">아파치 카멜</a></p></li><li><p><a href="https://camel.apache.org/manual/architecture.html">아파치 카멜 아키텍처</a></p></li><li><p><a href="https://camel.apache.org/components/4.4.x/eips/aggregate-eip.html">아파치 카멜 집계</a></p></li><li><p><a href="https://camel.apache.org/components/4.4.x/file-component.html">파일 구성 요소</a></p></li><li><p><a href="https://camel.apache.org/components/4.4.x/elasticsearch-component.html">Elasticsearch 구성 요소</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-apache-camel-ingest-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-apache-camel-ingest-data</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt05652327efd4d5d1/6a17e6dafbc5f8a588491a8b/bef8145623a8fa80f929f9faa57ce0c460be2d0b-884x458.png" length="0" type="image/png"/>
    <pubDate>Mon, 09 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>