<?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[Ruby - 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[Ruby - 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/ruby-programming</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/ruby-programming</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/ruby-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 15:07:24 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch Ruby 클라이언트를 위한 ES|QL 쿼리 빌더 소개]]></title>
    <description><![CDATA[최근 출시된 Elasticsearch Ruby 클라이언트용 ES|QL 쿼리 빌더를 사용하는 방법을 알아보세요. 루비 코드로 ES|QL 쿼리를 더 쉽게 작성할 수 있는 도구입니다.]]></description>
    <content:encoded><![CDATA[<p>최근에 Apache 2 라이선스에 따라 게시된 루비 보석( <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>)을 출시했습니다. 이 보석을 사용하면 관용적 Ruby로 Elastic의 <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> 쿼리를 빌드한 다음 ES|QL 쿼리 API와 함께 사용할 수 있습니다. ES|QL을 사용하면 개발자가 쿼리를 통해 Elasticsearch에 저장된 데이터를 필터링, 변환, 분석할 수 있습니다. "파이프" ( <code>|</code> )를 사용하여 단계별로 데이터를 작업합니다. 이 보석은 대신 Ruby 함수를 사용하며, 이를 원래 객체에 연결하여 더 복잡한 쿼리를 만들 수 있습니다:</p><p><strong>ESQL:</strong></p><p><strong>Ruby:</strong></p>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending<h2>설치</h2><p>이 젬은 루비젬스에서 다음을 사용하여 설치할 수 있습니다:</p>gem install elastic-esql<p>또는 프로젝트의 젬파일에 추가할 수도 있습니다:</p>gem 'elastic-esql'<h2>사용법</h2><p>전체 쿼리를 한 번에 빌드하거나 <code>from</code> 또는 <code>row</code> 같은 소스 명령으로 쿼리 개체를 만든 다음 ES|QL 메서드를 체인으로 연결하여 빌드할 수 있습니다.</p>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')<p>겜은 <code>to_s</code> 메서드에서 코드를 ES|QL로 변환하므로 ES|QL 쿼리가 출력되거나 문자열로 캐스팅될 때 반환합니다:</p>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"<p>각 함수의 <code>!</code> 등가물을 사용하여 쿼리 객체를 인스턴스화하고 초기 상태를 변경할 수 있습니다:</p>query = Elastic::ESQL.from('sample_data')
query.to_s
# =&gt; "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp"<p>이 도구는 <code>enrich</code> 및 <code>sort</code> 과 같은 추가 단계를 ES|QL 함수에 연결하는 편리한 방법을 제공합니다. <code>Elastic::ESQL</code> 객체에서 <code>enrich</code> 을 호출하면 <code>on</code> 과 <code>with</code> 을 연결할 수 있습니다:</p>esql.enrich!('policy').on('a').with({ name: 'language_name' })<p><code>sort</code> 을 사용한 후 <code>desc</code>, <code>asc</code>, <code>nulls_first</code>, <code>nulls_last</code> 을 쿼리에 연결할 수도 있습니다:</p>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# =&gt; 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# =&gt; 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'<p>또한 ES|QL 쿼리를 직접 작성하거나 아직 라이브러리에 추가되지 않은 기능을 사용하려는 경우 사용자 정의 문자열을 지원합니다. <code>custom</code> 은 쿼리 끝에 있는 문자열을 결합합니다. 파이프 문자를 추가하지 않고 함수에 전송되는 대로 추가합니다. 나머지 쿼리에는 공백 문자로 결합됩니다.</p>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# =&gt; 'FROM sample_data | MY_VALUE = "test value"'<p><code>custom</code> 함수를 연결할 수도 있습니다:</p>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'<h2>루비 클라이언트와 함께 ES|QL 쿼리 빌더 사용하기</h2><p>쿼리 빌더는 쿼리 객체를 전송하여 <a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a> 및 <code>esql.query</code> API와 함께 직접 사용할 수 있습니다:</p>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration &gt; 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })<p>Elasticsearch Ruby 클라이언트의 ES|QL 도우미와 함께 사용할 수도 있으며, <a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">자세히 알아보세요</a>:</p>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)<h2>독립형 도구로서</h2><p>이 보석은 관용적인 방식으로 ES|QL 쿼리를 작성하는 독립형 도구로 설계되었습니다. 런타임 종속성이 없으므로 공식 Elasticsearch Ruby 클라이언트와 함께 사용하거나 단독으로 사용할 수 있습니다.</p><p>생성된 쿼리는 애플리케이션이 Elasticsearch API와 상호 작용하는 모든 방식(Ruby 여부에 관계없이)으로 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> API와 함께 사용할 수 있습니다. <code>elastic-esql</code> 으로 쿼리를 작성하면 생성된 문자열을 요청 본문에 <code>query</code> 매개변수로 API에 전송할 수 있습니다. </p><p>이전에 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">인기 있는 Ruby 도구와 함께 Elasticsearch를 사용하는</a> 방법에 대한 글을 쓴 적이 있습니다. 이 보석은 널리 사용되는 모든 Ruby 도구와 함께 ES|QL로 Elasticsearch를 쿼리하는 데 사용할 수 있습니다.</p><h2>결론</h2><p>이 라이브러리는 현재 개발 중이며 최종 API는 아직 완성되지 않았습니다. 현재 기술 프리뷰 버전으로 출시되었습니다. 현재 API 또는 일반적인 사용법에 대한 피드백이 있으시면 주저하지 마시고 <a href="https://github.com/elastic/esql-ruby/issues">새 이슈를 개설해</a> 주세요. 루비 ES|QL 쿼리 빌더에 대해 자세히 알아보려면 <a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">README를</a> 참조하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85d112ccca541b9e/6a17dccb4b055d6bfd4320cc/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 17 Sep 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[OpenSearch에서 Elasticsearch로 Ruby 앱을 마이그레이션하는 방법]]></title>
    <description><![CDATA[OpenSearch 클라이언트에서 Elasticsearch 클라이언트로 Ruby 코드베이스를 마이그레이션하는 가이드입니다.]]></description>
    <content:encoded><![CDATA[<p>OpenSearch Ruby 클라이언트는 버전 <code>7.x</code> 에서 Elasticsearch Ruby 클라이언트에서 포크되었으므로 코드베이스가 비교적 유사합니다. 즉, OpenSearch에서 Elasticsearch로 Ruby 코드베이스를 마이그레이션할 때 각 클라이언트 라이브러리의 코드가 매우 친숙하게 보입니다. 이 블로그 포스팅에서는 OpenSearch를 사용하는 예제 Ruby 앱과 이 코드를 Elasticsearch로 마이그레이션하는 단계를 보여드리겠습니다.</p><p>두 클라이언트 모두 널리 사용되는 Apache 라이선스 2.0에 따라 출시되었으므로 오픈 소스 및 무료 소프트웨어입니다. Elasticsearch의 라이선스는 최근에 업데이트되었으며, 버전 8.16부터 Elasticsearch와 Kibana의 핵심은 OSI가 승인한 오픈 소스 라이선스 AGPL에 따라 게시되고 있습니다.</p><h2>Ruby 앱 마이그레이션 시 Elasticsearch 버전 고려하기</h2><p>마이그레이션할 때 고려해야 할 한 가지 사항은 어떤 버전의 Elasticsearch를 사용할 것인지입니다. 이 글을 작성하는 시점에 최신 안정 릴리스( <code>8.17.0</code>)를 사용하는 것이 좋습니다. Elasticsearch Ruby 클라이언트 부 버전은 Elasticsearch 부 버전을 따릅니다. 따라서 Elasticsearch <code>8.17.x</code> 의 경우 Ruby gem 버전 <code>8.17.x</code> 을 사용할 수 있습니다.</p><p>OpenSearch는 Elasticsearch 7.10.2에서 포크되었습니다. 따라서 API가 변경되어 다른 기능을 사용할 수 있습니다. 하지만 이는 이 글의 범위를 벗어나므로 예제 앱에서 가장 일반적인 작업만 살펴보겠습니다.</p><p>Ruby on Rails의 경우, 공식 Elasticsearch 클라이언트 또는 <a href="https://github.com/elastic/elasticsearch-rails/">Rails 통합 라이브러리를 사용할</a> 수 있습니다. 각각 안정적인 최신 버전의 Elasticsearch와 클라이언트로 마이그레이션하는 것이 좋습니다. <code>elasticsearch-rails</code> gem 버전 <code>8.0.0</code> 은 Rails <code>6.1</code>, <code>7.0</code> 및 <code>7.1</code> 와 Elasticsearch <code>8.x</code> 를 지원합니다.</p><h2>코드</h2><p>이 예제에서는 <a href="https://opensearch.org/docs/latest/install-and-configure/install-opensearch/tar/">타르볼에서 OpenSearch를 설치하는</a> 단계를 따랐습니다. 타르볼을 다운로드하고 압축을 푼 후, 나중에 클라이언트를 인스턴스화할 때 사용할 초기 관리자 비밀번호를 설정해야 했습니다.</p><p>다음과 같은 <code>Gemfile</code> 디렉터리를 만들었습니다:</p>source 'https://rubygems.org'

gem 'opensearch-ruby'<p><code>bundle install</code> 을 실행하면 내 프로젝트에 젬이 설치됩니다. 이 설치한 opensearch-ruby 버전은 <code>3.4.0</code> 이고 제가 실행 중인 OpenSearch 버전은 <code>2.18.0</code> 입니다. 같은 디렉토리에 있는 <code>example_code.rb</code> 파일에 코드를 작성했습니다. 이 파일의 초기 코드는 OpenSearch 클라이언트의 인스턴스화입니다:</p>require 'opensearch'

client = OpenSearch::Client.new(
  host: 'https://localhost:9200',
  user: 'admin',
  password: ENV['OPENSEARCH_INITIAL_ADMIN_PASSWORD'],
  transport_options: { ssl: { verify: false } }
)<p>테스트를 쉽게 하기 위해 사용자 가이드에 따라 전송 옵션 <code>ssl: { verify: false}</code> 매개변수를 전달하고 있습니다. 프로덕션 환경에서는 OpenSearch 배포에 따라 설정해야 합니다.</p><p>OpenSearch 버전 2.12.0부터 설치 스크립트를 실행할 때 <code>OPENSEARCH_INITIAL_ADMIN_PASSWORD</code> 환경 변수를 강력한 비밀번호로 설정해야 합니다. 타르볼에서 OpenSearch를 설치하는 단계에 따라 콘솔에서 변수를 내보냈고 이제 Ruby 스크립트에서 사용할 수 있습니다.</p><p>클라이언트가 OpenSearch에 연결하고 있는지 확인하기 위한 간단한 API는 <code>cluster.health</code> API를 사용합니다:</p>puts 'HEALTH:'
pp client.cluster.health<p>그리고 실제로 효과가 있습니다:</p>$ be ruby example_code.rb
HEALTH:
{"cluster_name"=&gt;"opensearch",
"status"=&gt;"yellow",
 "timed_out"=&gt;false,
 "number_of_nodes"=&gt;1,
 "number_of_data_nodes"=&gt;1,<p>Elasticsearch Ruby 클라이언트 문서에 있는 몇 가지 일반적인 예제를 테스트해 본 결과 예상대로 작동했습니다:</p>index = 'books'
puts 'Creating index'
response = client.indices.create(index: index)
puts response
# Creating index
# {"acknowledged"=&gt;true, "shards_acknowledged"=&gt;true, "index"=&gt;"books"}

puts 'Indexing a document'
document = { title: 'The Time Machine', author: 'H. G. Wells', year: 1895 }
response = client.index(index: index, body: document, refresh: true)
puts response
# Indexing document
# {"_index"=&gt;"books", "_id"=&gt;"esalT5MB4vnuJz5TtqOc", "_version"=&gt;1, "result"=&gt;"created", "forced_refresh"=&gt;true, "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;0, "_primary_term"=&gt;1}

id = response['_id']
puts 'Getting document'
response = client.get(index: index, id: id)
puts response
# Getting document
# {"_index"=&gt;"books", "_id"=&gt;"esalT5MB4vnuJz5TtqOc", "_version"=&gt;1, "_seq_no"=&gt;0, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"title"= &gt;"The Time Machine", "author"=&gt;"H. G. Wells", "year"=&gt;1895}}

puts "Does an index exist?"
puts client.indices.exists(index: 'imaginary_index')
# Does an index exist?
# false

puts 'Processing Bulk request'
body = [
  { index: { _index: 'books', data: { name: 'Leviathan Wakes', author: 'James S.A. Corey', release_date: '2011-06-02', page_count: 561 } } },
  { index: { _index: 'books', data: { name: 'Hyperion', author: 'Dan Simmons', release_date: '1989-05-26', page_count: 482 } } },
  { index: { _index: 'books', data: { name: 'Dune', author: 'Frank Herbert', release_date: '1965-06-01', page_count: 604 } } },
  { index: { _index: 'books', data: { name: 'Dune Messiah', author: 'Frank Herbert', release_date: '1969-10-15', page_count: 331 } } },
  { index: { _index: 'books', data: { name: 'Children of Dune', author: 'Frank Herbert', release_date: '1976-04-21', page_count: 408 } } },
  { index: { _index: 'books', data: { name: 'God Emperor of Dune', author: 'Frank Herbert', release_date: '1981-05-28', page_count: 454 } } },
  { index: { _index: 'books', data: { name: 'Consider Phlebas', author: 'Iain M. Banks', release_date: '1987-04-23', page_count: 471 } } },
  { index: { _index: 'books', data: { name: 'Pandora\'s Star', author: 'Peter F. Hamilton', release_date: '2004-03-02', page_count: 768 } } },
  { index: { _index: 'books', data: { name: 'Revelation Space', author: 'Alastair Reynolds', release_date: '2000-03-15', page_count: 585 } } },
  { index: { _index: 'books', data: { name: 'A Fire Upon the Deep', author: 'Vernor Vinge', release_date: '1992-06-01', page_count: 613 } } },
  { index: { _index: 'books', data: { name: 'Ender\'s Game', author: 'Orson Scott Card', release_date: '1985-06-01', page_count: 324 } } },
  { index: { _index: 'books', data: { name: '1984', author: 'George Orwell', release_date: '1985-06-01', page_count: 328 } } },
  { index: { _index: 'books', data: { name: 'Fahrenheit 451', author: 'Ray Bradbury', release_date: '1953-10-15', page_count: 227 } } },
  { index: { _index: 'books', data: { name: 'Brave New World', author: 'Aldous Huxley', release_date: '1932-06-01', page_count: 268 } } },
  { index: { _index: 'books', data: { name: 'Foundation', author: 'Isaac Asimov', release_date: '1951-06-01', page_count: 224 } } },
  { index: { _index: 'books', data: { name: 'The Giver', author: 'Lois Lowry', release_date: '1993-04-26', page_count: 208 } } },
  { index: { _index: 'books', data: { name: 'Slaughterhouse-Five', author: 'Kurt Vonnegut', release_date: '1969-06-01', page_count: 275 } } },
  { index: { _index: 'books', data: { name: 'The Hitchhiker\'s Guide to the Galaxy', author: 'Douglas Adams', release_date: '1979-10-12', page_count: 180 } } },
  { index: { _index: 'books', data: { name: 'Snow Crash', author: 'Neal Stephenson', release_date: '1992-06-01', page_count: 470 } } },
  { index: { _index: 'books', data: { name: 'Neuromancer', author: 'William Gibson', release_date: '1984-07-01', page_count: 271 } } },
  { index: { _index: 'books', data: { name: 'The Handmaid\'s Tale', author: 'Margaret Atwood', release_date: '1985-06-01', page_count: 311 } } },
  { index: { _index: 'books', data: { name: 'Starship Troopers', author: 'Robert A. Heinlein', release_date: '1959-12-01', page_count: 335 } } },
  { index: { _index: 'books', data: { name: 'The Left Hand of Darkness', author: 'Ursula K. Le Guin', release_date: '1969-06-01', page_count: 304 } } },
  { index: { _index: 'books', data: { name: 'The Moon is a Harsh Mistress', author: 'Robert A. Heinlein', release_date: '1966-04-01', page_count: 288 } } }
]
puts client.bulk(body: body, refresh: true)
# Processing Bulk request
# {"took"=&gt;38, "errors"=&gt;false, "items"=&gt;[{"index"=&gt;{"_index"=&gt;"books", "_id"=&gt;" ...

query = { query: { multi_match: { query: 'dune', fields: ['name'] } } }
puts 'Search results'
response = client.search(index: index, body: query)
puts response
# Search results
# {"_index"=&gt;"books", "_id"=&gt;"oEawT5MBOXHuGXdEu5Wu", "_score"=&gt;2.2886353, "_source"=&gt;{"name"=&gt;"Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1965-06-01", "page_count"=&gt;604}}
# {"_index"=&gt;"books", "_id"=&gt;"oUawT5MBOXHuGXdEu5Wu", "_score"=&gt;1.8893257, "_source"=&gt;{"name"=&gt;"Dune Messiah", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1969-10-15", "page_count"=&gt;331}}
# {"_index"=&gt;"books", "_id"=&gt;"okawT5MBOXHuGXdEu5Wu", "_score"=&gt;1.6086557, "_source"=&gt;{"name"=&gt;"Children of Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1976-04-21", "page_count"=&gt;408}}
# {"_index"=&gt;"books", "_id"=&gt;"o0awT5MBOXHuGXdEu5Wu", "_score"=&gt;1.40059, "_source"=&gt;{"name"=&gt;"God Emperor of Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1981-05-28", "page_count"=&gt;454}}

puts 'Updating document'
document = { title: 'Walkaway', author: 'Cory Doctorow', release_date: '2017' }
response = client.index(index: index, body: document, refresh: true)
id = response['_id']
response = client.update(index: index, id: id, body: { doc: { release_date: '2017-04-26' } })
puts response
# Updating document
# {"_index"=&gt;"books", "_id"=&gt;"degnZJMBIGr4X0Yim55L", "_version"=&gt;2, "result"=&gt;"updated", "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;26, "_primary_term"=&gt;1}

puts 'Retrieveing multiple documents'
response = client.search(index: index, body: { query: { match_all: {} }, size: 3, stored_fields: '_id' })
ids = response['hits']['hits']
ids.map { |a| a.delete('_score') }
response = client.mget(body: { docs: [{ _index: index, _id: ids }] })
puts response
# Retrieveing multiple documents
# {"docs"=&gt;[{"_index"=&gt;"books", "_id"=&gt;"qeg2ZJMBIGr4X0YiiqD2", "_version"=&gt;1, "_seq_no"=&gt;0, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"title"=&gt;"The Time Machine", "author"=&gt;"H. G. Wells", "year"=&gt;1895}}, {"_index"=&gt;"books", "_id"=&gt;"q-g2ZJMBIGr4X0Yii6Ah", "_version"=&gt;1, "_seq_no"=&gt;1, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"name"=&gt;"Leviathan Wakes", "author"=&gt;"James S.A. Corey", "release_date"=&gt;"2011-06-02", "page_count"=&gt;561}}, {"_index"=&gt;"books", "_id"=&gt;"rOg2ZJMBIGr4X0Yii6Ah", "_version"=&gt;1, "_seq_no"=&gt;2, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"name"=&gt;"Hyperion", "author"=&gt;"Dan Simmons", "release_date"=&gt;"1989-05-26", "page_count"=&gt;482}}]}

puts "Count #{client.count(index: index)['count']}"
puts 'Deleting by query'
response = client.delete_by_query(index: index, body: { query: { match: { author: 'Robert A. Heinlein' } } }, refresh: true)
puts response
puts "Count #{client.count(index: index)['count']}"
# Count 26
# Deleting by query
# {"took"=&gt;16, "timed_out"=&gt;false, "total"=&gt;2, "deleted"=&gt;2, "batches"=&gt;1, "version_conflicts"=&gt;0, "noops"=&gt;0, "retries"=&gt;{"bulk"=&gt;0, "search"=&gt;0}, "throttled_millis"=&gt;0, "requests_per_second"=&gt;-1.0, "throttled_until_millis"=&gt;0, "failures"=&gt;[]}
# Count 24

puts 'Deleting document'
response = client.delete(index: index, id: id)
puts response
# Deleting document
# {"_index"=&gt;"books", "_id"=&gt;"nEawT5MBOXHuGXdEu5WA", "_version"=&gt;2, "result"=&gt;"deleted", "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;25, "_primary_term"=&gt;1}

puts 'Deleting index'
response = client.indices.delete(index: index)
puts response
# Deleting index
# {"acknowledged"=&gt;true}<h2>Ruby 앱을 Elasticsearch로 마이그레이션하기</h2><p>첫 번째 단계는 젬파일에 <code>elasticsearch-ruby</code> 을 추가하는 것입니다. <code>bundle install</code> 을 실행하면 Elasticsearch Ruby 클라이언트 젬이 설치됩니다. 완전히 마이그레이션하기 전에 코드를 테스트하고 싶다면 처음에는 <code>opensearch-ruby</code> gem을 그대로 두면 됩니다.</p><p>다음으로 중요한 단계는 클라이언트 인스턴스화입니다. 이것은 Elasticsearch를 실행하는 방식에 따라 달라집니다. 이 예제에서 유사한 접근 방식을 유지하기 위해, 저는 <a href="https://www.elastic.co/downloads/elasticsearch">Elasticsearch 다운로드</a> 및 로컬 실행의 단계를 따르고 있습니다.</p><p><code>bin/elasticsearch</code> 을 실행하면 보안 기능이 자동으로 구성된 상태로 Elasticsearch가 시작됩니다. Elastic 사용자의 비밀번호를 복사해야 합니다(하지만 <code>bin/elasticsearch-reset-password -u elastic</code>)를 실행하여 재설정할 수 있습니다. 이 예제를 따르는 경우, 동일한 포트에서 실행되므로 Elasticsearch를 시작하기 전에 OpenSearch를 중지해야 합니다.</p><p><code>example_code.rb</code> 의 시작 부분에서 OpenSearch 클라이언트 인스턴스화를 주석 처리하고 Elasticsearch 클라이언트에 대한 인스턴스화를 추가했습니다:</p># require 'opensearch'

# client = OpenSearch::Client.new(
#   host: 'https://localhost:9200',
#   user: 'admin',
#   password: ENV['OPENSEARCH_INITIAL_ADMIN_PASSWORD']
#   transport_options: { ssl: { verify: false } }
# )

require 'elasticsearch'

client = Elasticsearch::Client.new(
  host: 'https://localhost:9200',
  user: ENV['ELASTICSEARCH_USER'],
  password: ENV['ELASTICSEARCH_PASSWORD'],
  transport_options: { ssl: { verify: false } }
)<p>보시다시피 이 테스트 시나리오의 코드는 거의 동일합니다. Elasticsearch의 배포와 연결 및 인증 방법에 따라 달라집니다. 여기에서도 보안과 관련하여 OpenSearch와 동일하게 적용되며, SSL을 확인하지 않는 옵션은 테스트 목적으로만 사용되며 운영 환경에서는 사용해서는 안 됩니다.</p><p>클라이언트가 설정되면 코드를 다시 실행합니다: <code>bundle exec ruby example_code.rb</code>. 그리고 모든 것이 작동합니다!</p><h2>마이그레이션 문제 디버깅</h2><p>애플리케이션에서 사용 중인 API에 따라, OpenSearch의 API가 서로 다른 경우 Elasticsearch에서 코드를 실행할 때 오류가 발생할 수 있습니다. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html">REST API 문서는</a> API 사용 방법에 대한 자세한 정보를 얻기 위한 필수 참고 자료입니다. 사용 중인 Elasticsearch 버전에 대한 설명서를 확인하시기 바랍니다. <a href="https://rubydoc.info/gems/elasticsearch-api"><code>Elasticsearch::API</code></a> 참조를 참조할 수도 있습니다.</p><p>Elasticsearch에서 발생할 수 있는 몇 가지 오류는 다음과 같습니다:</p><ul><li><p><code>ArgumentError: Required argument '&lt;ARGUMENT&gt;' missing</code> - 클라이언트 오류이며 요청에 필수 매개변수가 누락된 경우 발생합니다.</p></li><li><p><code>Elastic::Transport::Transport::Errors::BadRequest: [400] {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"request [/example/_doc] contains unrecognized parameter: [test]"}]...</code> 이 오류는 Elasticsearch에서 발생하며 클라이언트 코드가 사용 중인 API에 대해 Elasticsearch가 인식하지 못하는 매개 변수를 사용하고 있음을 의미합니다.</p></li></ul><p>Elasticsearch 클라이언트는 서버에서 전송한 자세한 오류 메시지와 함께 Elasticsearch에서 오류를 발생시킵니다. 따라서 지원되지 않는 매개변수나 엔드포인트의 경우에도 오류를 통해 무엇이 다른지 알 수 있습니다.</p><h2>결론</h2><p>이 예제 코드에서 보여드렸듯이, Ruby 앱을 OpenSearch에서 Elasticsearch로 마이그레이션하는 것은 Ruby 측면에서 볼 때 그리 복잡하지 않습니다. 검색 엔진 간의 버전 관리 및 잠재적으로 서로 다른 API를 알고 있어야 합니다. 그러나 가장 일반적인 작업의 경우 클라이언트를 마이그레이션할 때 주로 변경되는 부분은 인스턴스화입니다. 이 점에서 둘은 비슷하지만 호스트와 자격 증명이 정의되는 방식은 스택이 배포되는 방식에 따라 다릅니다. 클라이언트가 설정되고 Elasticsearch에 연결되는 것을 확인하면 OpenSearch 클라이언트를 Elasticsearch 클라이언트로 원활하게 교체할 수 있습니다.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ruby-opensearch-elasticsearch-migration</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ruby-opensearch-elasticsearch-migration</guid>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f2ce1fb5cbf7d70/6a17e1dd148009bc02b486af/47756f629737d47c4430860ea23366a8d24c90d9-1280x720.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>