본문 바로가기
WIKI 기술 지식 베이스

Ruby client

원문 보기 위키 갱신

출처: 문서

본문

Ruby 클라이언트

OpenSearch Ruby 클라이언트를 사용하면 HTTP 메서드와 raw JSON 대신 Ruby 메서드를 통해 OpenSearch 클러스터와 상호작용할 수 있어요. 클라이언트의 전체 API 문서는 opensearch-ruby 저장소 문서를 참고하세요. 추가 예제는 opensearch-transport, opensearch-api, opensearch-dsl, opensearch-ruby gem 문서를 참고하세요.

이 시작 가이드에서는 OpenSearch에 연결하고, 문서를 인덱싱하고, 쿼리를 실행하는 방법을 설명해요. 클라이언트 소스 코드는 opensearch-ruby 저장소를 참고하세요.

Ruby 클라이언트 설치

Ruby 클라이언트용 Ruby gem을 설치하려면 다음 명령어를 실행하세요:

gem install opensearch-ruby

{% include copy.html %}

또는 gem을 Gemfile에 추가하고 bundle install을 실행해도 돼요. 다음 예제에는 클라이언트 버전 3.4.0 또는 그 이후의 3.x 릴리스가 필요해요:

gem 'opensearch-ruby', '~> 3.4'

{% include copy.html %}

클라이언트를 사용하려면 모듈로 임포트하세요:

require 'opensearch'

{% include copy.html %}

OpenSearch에 연결

기본 OpenSearch 호스트에 연결하려면, 생성자에 기본 호스트 주소를 전달하면서 클라이언트 객체를 생성하세요:

client = OpenSearch::Client.new(host: 'http://localhost:9200')

{% include copy.html %}

다음 예제는 커스텀 URL과 true로 설정한 log 옵션으로 클라이언트 객체를 생성해요. retry_on_failure 파라미터를 설정해 실패한 요청을 기본값인 세 번이 아닌 다섯 번 재시도하도록 해요. 마지막으로 request_timeout 파라미터를 120초로 설정해 타임아웃을 늘려요. 그런 다음 기본 클러스터 상태 정보를 반환해요:

client = OpenSearch::Client.new(
    url: "http://localhost:9200",
    retry_on_failure: 5,
    request_timeout: 120,
    log: true
  )

client.cluster.health

{% include copy.html %}

출력 결과는 다음과 같아요:

2026-09-30 12:29:58 -0400: GET http://localhost:9200/ [status:200, request:0.009s, query:n/a]
2026-09-30 12:29:58 -0400: < {
  "name" : "opensearch-node1",
  "cluster_name" : "opensearch-cluster",
  "cluster_uuid" : "uwutrwdfTVeh8rroYbZh4g",
  "version" : {
    "distribution" : "opensearch",
    "number" : "3.8.0",
    "build_type" : "tar",
    "build_hash" : "e5a3c5691be87af6c12dbe3e158c59c04ee72973",
    "build_date" : "2026-08-03T21:07:36.443334696Z",
    "build_snapshot" : false,
    "lucene_version" : "10.5.0",
    "minimum_wire_compatibility_version" : "2.19.0",
    "minimum_index_compatibility_version" : "2.0.0"
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}

2026-09-30 12:29:58 -0400: GET http://localhost:9200/_cluster/health [status:200, request:0.007s, query:n/a]
2026-09-30 12:29:58 -0400: < {"cluster_name":"opensearch-cluster","status":"yellow","timed_out":false,"number_of_nodes":1,"number_of_data_nodes":1,"discovered_master":true,"discovered_cluster_manager":true,"active_primary_shards":57,"active_shards":57,"relocating_shards":0,"initializing_shards":0,"unassigned_shards":28,"delayed_unassigned_shards":0,"number_of_pending_tasks":0,"number_of_in_flight_fetch":0,"task_max_waiting_in_queue_millis":0,"active_shards_percent_as_number":67.05882352941175}

Security 플러그인이 활성화된 클러스터에 연결하려면 HTTPS를 사용하고 사용자 자격 증명을 제공하세요:

client = OpenSearch::Client.new(
    host: 'https://localhost:9200',
    user: 'admin', # Only for demo purposes. Don't specify your credentials in code.
    password: '<custom-admin-password>',
    transport_options: { ssl: { verify: false } } # For testing only. Use a certificate for validation.
)

{% include copy.html %}

Amazon OpenSearch Service에 연결

Amazon OpenSearch Service에 연결하려면 먼저 opensearch-aws-sigv4 gem을 설치하세요:

gem install opensearch-aws-sigv4

{% include copy.html %}

그다음 클라이언트를 생성하세요. 엔드포인트는 Amazon OpenSearch Service 콘솔의 도메인 세부 정보 페이지에 표시된 도메인 엔드포인트로 바꾸세요:

require 'opensearch-aws-sigv4'
require 'aws-sigv4'

signer = Aws::Sigv4::Signer.new(service: 'es',
                                region: 'us-east-1', # must match the Region in the endpoint
                                access_key_id: 'key_id',
                                secret_access_key: 'secret',
                                session_token: 'session_token') # required for temporary credentials, such as IAM roles or SSO

client = OpenSearch::Aws::Sigv4Client.new({
    host: 'https://search-<domain-name>-<id>.us-east-1.es.amazonaws.com',
    log: true
}, signer)

# create an index and document
index = 'students'
client.indices.create(index: index)
client.index(index: index, id: '1', body: { firstName: 'John',
                                            lastName: 'Doe',
                                            gpa: 3.89,
                                            gradDate: '2022-05-15' },
                                            refresh: true)

# search for the document
client.search(index: index, body: { query: { match: { firstName: 'John' } } })

# delete the document
client.delete(index: index, id: '1')

# delete the index
client.indices.delete(index: index)

{% include copy.html %}

Amazon OpenSearch Serverless에 연결

Amazon OpenSearch Serverless에 연결하려면 먼저 opensearch-aws-sigv4 gem을 설치하세요:

gem install opensearch-aws-sigv4

{% include copy.html %}

그다음 클라이언트를 생성하세요. 엔드포인트는 Amazon OpenSearch Service 콘솔의 컬렉션 세부 정보 페이지에 표시된 컬렉션 엔드포인트로 바꾸세요:

require 'opensearch-aws-sigv4'
require 'aws-sigv4'

signer = Aws::Sigv4::Signer.new(service: 'aoss',
                                region: 'us-east-1', # must match the Region in the endpoint
                                access_key_id: 'key_id',
                                secret_access_key: 'secret',
                                session_token: 'session_token') # required for temporary credentials, such as IAM roles or SSO

client = OpenSearch::Aws::Sigv4Client.new({
    host: 'https://<collection-id>.us-east-1.aoss.amazonaws.com', # Amazon OpenSearch Serverless collection endpoint
    log: true
}, signer)

# check whether an index exists
puts client.indices.exists?(index: 'students')

{% include copy.html %}

Amazon OpenSearch Serverless는 OpenSearch API 연산 중 일부만 지원하며, 이 페이지의 예제에서 사용하는 refresh 파라미터는 지원하지 않아요. 자세한 내용은 Amazon OpenSearch Serverless에서 지원되는 작업 및 플러그인을 참고하세요. {: .note}

인덱스 생성

OpenSearch에서는 인덱스를 명시적으로 생성할 필요가 없어요. 존재하지 않는 인덱스에 문서를 업로드하면 OpenSearch가 자동으로 인덱스를 생성해 줘요. 인덱스를 명시적으로 생성하려면 indices.create 메서드를 사용하고 body 파라미터에 인덱스 설정과 매핑을 전달하세요.

다음 예제는 프라이머리 샤드 하나와 레플리카 하나를 가진 인덱스를 생성해요. gradDate 필드를 yyyy-MM-dd 형식의 date로 명시적으로 매핑해요. 문서를 인덱싱할 때 나머지 문서 필드는 OpenSearch가 동적으로 매핑해요:

index_body = {
  settings: {
    index: {
      number_of_shards: 1,
      number_of_replicas: 1
    }
  },
  mappings: {
    properties: {
      gradDate: { type: 'date', format: 'yyyy-MM-dd' }
    }
  }
}
client.indices.create(index: 'students', body: index_body)

{% include copy.html %}

매핑

OpenSearch는 동적 매핑을 사용해 인덱싱되는 문서의 필드 타입을 추론해요. 하지만 문서의 스키마를 더 세밀하게 제어하고 싶다면 인덱스 생성에서 본 것처럼 명시적 매핑을 OpenSearch에 전달할 수 있어요. 기본적으로 문자열 필드는 text로 매핑돼요. 대신 필드를 keyword로 매핑하면, 그 필드는 분석되지 않아야 하며 대소문자를 구분하는 전체 일치만 지원해야 한다고 OpenSearch에 알려주는 것이 돼요.

인덱스의 매핑을 확인하려면 get_mapping 메서드를 사용하세요:

response = client.indices.get_mapping(index: 'students')

{% include copy.html %}

문서의 매핑을 미리 알고 있고 매핑 오류(예: 필드 이름 오타)를 피하고 싶다면, put_mapping 메서드로 나머지 필드를 매핑하고 dynamic 파라미터를 strict로 설정하세요:

client.indices.put_mapping(
  index: 'students',
  body: {
    dynamic: 'strict',
    properties: {
      firstName: { type: 'keyword' },
      lastName: { type: 'keyword' },
      gpa: { type: 'float' },
      gradDate: { type: 'date', format: 'yyyy-MM-dd' }
    }
  }
)

{% include copy.html %}

strict 매핑에서는 필드가 누락된 문서는 인덱싱할 수 있지만, 새로운 필드가 있는 문서는 인덱싱할 수 없어요. 예를 들어 오타가 있는 gradDat 필드를 포함한 다음 문서를 인덱싱하면 실패해요:

student = { firstName: 'John', lastName: 'Doe', gpa: 3.89, gradDat: '2022-05-15' }
client.index(index: 'students', id: '1', body: student, refresh: true)

{% include copy.html %}

OpenSearch는 매핑 오류를 반환하고, 클라이언트는 다음 메시지를 담은 OpenSearch::Transport::Transport::Errors::BadRequest 예외를 발생시켜요:

[400] {"error":{"root_cause":[{"type":"strict_dynamic_mapping_exception","reason":"mapping set to strict, dynamic introduction of [gradDat] within [_doc] is not allowed"}],"type":"strict_dynamic_mapping_exception","reason":"mapping set to strict, dynamic introduction of [gradDat] within [_doc] is not allowed"},"status":400}

문서 인덱싱

문서를 인덱싱하려면 index 메서드를 사용하세요:

student = { firstName: 'John', lastName: 'Doe', gpa: 3.89, gradDate: '2022-05-15' }
response = client.index(index: 'students', id: '1', body: student, refresh: true)

{% include copy.html %}

벌크 연산

bulk 메서드를 사용하면 여러 연산을 한 번에 수행할 수 있어요. 연산은 같은 타입일 수도 있고 서로 다른 타입일 수도 있어요.

여러 문서를 인덱싱하려면 각 액션 헤더와 그에 해당하는 문서를 차례로 전달하세요:

actions = [
  { index: { _index: 'students', _id: '2' } },
  { firstName: 'Paulo', lastName: 'Santos', gpa: 3.93, gradDate: '2021-05-20' },
  { index: { _index: 'students', _id: '3' } },
  { firstName: 'Shirley', lastName: 'Rodriguez', gpa: 3.91, gradDate: '2019-05-10' }
]
response = client.bulk(body: actions, refresh: true)

{% include copy.html %}

또는 data: 키로 데이터를 표시해서 헤더와 데이터를 함께 전달할 수도 있어요. 다음 요청은 동일한 두 문서를 인덱싱해요:

actions = [
  { index: { _index: 'students', _id: '2', data: { firstName: 'Paulo', lastName: 'Santos', gpa: 3.93, gradDate: '2021-05-20' } } },
  { index: { _index: 'students', _id: '3', data: { firstName: 'Shirley', lastName: 'Rodriguez', gpa: 3.91, gradDate: '2019-05-10' } } }
]
response = client.bulk(body: actions, refresh: true)

{% include copy.html %}

문서 검색

문서를 검색하려면 search 메서드를 사용하세요. 요청 본문을 생략하면 쿼리가 match_all 쿼리가 되어 인덱스의 모든 문서를 반환해요:

require 'json'

response = client.search(index: 'students')
response['hits']['hits'].each { |hit| puts JSON.generate(hit['_source']) }

{% include copy.html %}

다음 예제는 range 쿼리를 사용해 2019년에 졸업한 학생을 검색해요:

query = { query: { range: { gradDate: { gte: '2019-01-01', lte: '2019-12-31' } } } }
response = client.search(index: 'students', body: query)

{% include copy.html %}

다음 예제는 이름이나 성이 "Santos"인 학생을 검색해요. multi_match 쿼리를 사용해 두 필드(firstName과 lastName)를 검색하고, 캐럿 표기법(lastName^2)으로 lastName 필드의 관련성을 부스팅해요:

query = {
  size: 5,
  query: {
    multi_match: {
      query: 'Santos',
      fields: ['firstName', 'lastName^2']
    }
  }
}
response = client.search(index: 'students', body: query)

{% include copy.html %}

불리언 쿼리

Ruby 클라이언트는 OpenSearch의 전체 쿼리 기능을 제공해요. match 쿼리를 사용하는 간단한 검색 외에도, 더 복잡한 불리언 쿼리를 만들어 2021년 이후에 졸업한 학생을 검색하고 GPA 내림차순으로 정렬할 수 있어요. 다음 예제에서는 검색을 10개 문서로 제한해요:

query = {
  query: {
    bool: {
      filter: {
        range: {
          gradDate: { gte: '2021-01-01' }
        }
      }
    }
  },
  sort: {
    gpa: { order: 'desc' }
  }
}
response = client.search(index: 'students', from: 0, size: 10, body: query)

{% include copy.html %}

멀티 검색

여러 쿼리를 한꺼번에 묶어서 msearch 메서드로 멀티 검색을 수행할 수 있어요. 다음 코드는 GPA가 3.9보다 큰 학생과 GPA가 3.9보다 작은 학생을 검색해요:

actions = [
  {},
  { query: { range: { gpa: { gt: 3.9 } } } },
  {},
  { query: { range: { gpa: { lt: 3.9 } } } }
]
response = client.msearch(index: 'students', body: actions)

{% include copy.html %}

결과 페이지네이션

결과를 페이지네이션하려면 from과 size 파라미터를 사용하세요. 다음 예제는 학생을 졸업 날짜 기준으로 정렬하고 결과를 한 번에 두 개씩 가져와요. 첫 번째 요청은 결과의 첫 페이지를 반환하고, 두 번째 요청은 다음 페이지를 반환해요:

require 'json'

query = { sort: [{ gradDate: 'asc' }] }
response = client.search(index: 'students', from: 0, size: 2, body: query)
response['hits']['hits'].each { |hit| puts JSON.generate(hit['_source']) }

response = client.search(index: 'students', from: 2, size: 2, body: query)
response['hits']['hits'].each { |hit| puts JSON.generate(hit['_source']) }

{% include copy.html %}

from과 size 파라미터는 결과의 첫 몇 페이지를 다룰 때 잘 동작해요. 대량의 결과를 배치로 처리하려면 scroll을 사용한 페이지네이션에서 설명하는 것처럼 scroll을 사용하세요.

scroll을 사용한 페이지네이션

검색 결과를 페이지네이션하려면 Scroll API를 사용하세요. Scroll은 클러스터에서 검색 컨텍스트를 열어 둔 상태로 유지하기 때문에, 사용자 대상 요청보다는 배치 작업에서 모든 결과를 처리하는 데 적합해요. 다른 사용 사례에서는 point in time을 search_after와 함께 사용하세요. 자세한 내용은 결과 페이지네이션을 참고하세요.

다음 예제는 모든 학생을 한 번에 두 명씩 가져와요:

response = client.search(index: 'students', scroll: '2m', size: 2)

while response['hits']['hits'].size.positive?
  scroll_id = response['_scroll_id']
  puts(response['hits']['hits'].map { |hit| "#{hit['_source']['firstName']} #{hit['_source']['lastName']}" })
  response = client.scroll(scroll: '1m', body: { scroll_id: scroll_id })
end

client.clear_scroll(body: { scroll_id: response['_scroll_id'] })

{% include copy.html %}

먼저 scroll과 size 파라미터를 지정해서 검색 쿼리를 실행해요. scroll 파라미터는 검색 컨텍스트를 얼마나 오래 유지할지 OpenSearch에 알려줘요. 여기서는 2분으로 설정되어 있어요. size 파라미터는 각 요청에서 반환받을 문서 수를 지정해요.

최초 검색 쿼리에 대한 응답에는 _scroll_id가 포함되어 있고, 이를 사용해 다음 문서 세트를 가져올 수 있어요. 이를 위해 scroll 메서드를 사용하면서 다시 scroll 파라미터를 지정하고 본문에 _scroll_id를 전달해요. scroll 메서드에는 쿼리나 인덱스를 지정할 필요가 없어요. scroll 메서드는 다음 문서 세트와 _scroll_id를 반환해요. _scroll_id는 요청 사이에서 변경될 수 있기 때문에, 다음 문서 배치를 요청할 때는 가장 최신의 _scroll_id를 사용하는 것이 중요해요. 모든 문서를 가져왔다면 clear_scroll 메서드를 사용해 검색 컨텍스트를 해제하세요.

문서 업데이트

문서를 업데이트하려면 update 메서드를 사용하고 변경할 필드를 doc 객체에 담아 전달하세요. 그런 다음 get 메서드로 업데이트된 문서를 조회해요:

response = client.update(index: 'students', id: '1', body: { doc: { gpa: 3.92 } })
response = client.get(index: 'students', id: '1')

{% include copy.html %}

문서 삭제

문서를 삭제하려면 delete 메서드를 사용하세요:

response = client.delete(index: 'students', id: '3', refresh: true)

{% include copy.html %}

여러 문서를 한 번의 요청으로 삭제하려면 bulk 메서드를 사용하세요:

actions = [
  { delete: { _index: 'students', _id: '1' } },
  { delete: { _index: 'students', _id: '2' } }
]
response = client.bulk(body: actions, refresh: true)

{% include copy.html %}

인덱스 삭제

인덱스를 삭제하려면 indices.delete 메서드를 사용하세요:

response = client.indices.delete(index: 'students')

{% include copy.html %}

샘플 프로그램

이 샘플 프로그램은 앞선 섹션들의 코드를 합친 것이에요. Security 플러그인이 활성화된 클러스터에 연결해요. Security 플러그인이 없는 클러스터에 연결하려면 # Without security 주석으로 표시된 줄을 변경하세요.

이 샘플 프로그램은 테스트 전용이에요. 자체 서명된 인증서를 사용하는 클러스터에 연결할 수 있도록 코드에 자격 증명을 지정하고 인증서 검증을 비활성화해요. 프로덕션 환경에서는 자격 증명을 안전한 위치에서 로드하고 클러스터의 인증서를 검증하세요. {: .warning}

다음 샘플 프로그램은 클라이언트를 생성하고, 인덱스를 생성하고, 문서를 개별적으로 그리고 벌크로 인덱싱하고, 문서를 검색하고, 문서를 업데이트하고, 문서를 삭제한 다음 인덱스를 삭제해요:

require 'opensearch'
require 'json'

client = OpenSearch::Client.new(
  host: 'https://localhost:9200', # Without security, use http://localhost:9200
  user: 'admin', # Without security, remove this line
  password: '<custom-admin-password>', # Without security, remove this line
  transport_options: { ssl: { verify: false } } # Without security, remove this line
)

# Create the index
index = 'students'
puts 'Creating index......'
index_body = {
  settings: {
    index: {
      number_of_shards: 1,
      number_of_replicas: 1
    }
  },
  mappings: {
    properties: {
      gradDate: { type: 'date', format: 'yyyy-MM-dd' }
    }
  }
}
response = client.indices.create(index: index, body: index_body)
puts "Index created: #{response['index']}"

# Index a document
puts "\nIndexing one student......"
student = { firstName: 'John', lastName: 'Doe', gpa: 3.89, gradDate: '2022-05-15' }
response = client.index(index: index, id: '1', body: student, refresh: true)
puts "Result: #{response['result']}, id: #{response['_id']}, version: #{response['_version']}"

# Bulk index documents
puts "\nIndexing many students......"
actions = [
  { index: { _index: index, _id: '2' } },
  { firstName: 'Paulo', lastName: 'Santos', gpa: 3.93, gradDate: '2021-05-20' },
  { index: { _index: index, _id: '3' } },
  { firstName: 'Shirley', lastName: 'Rodriguez', gpa: 3.91, gradDate: '2019-05-10' }
]
response = client.bulk(body: actions, refresh: true)
puts "Errors: #{response['errors']}"
response['items'].each do |item|
  puts "  #{item['index']['result']} id: #{item['index']['_id']}"
end

# Search for all students
puts "\nSearching for all students......"
query = { sort: [{ gradDate: 'asc' }] }
[0, 2].each_with_index do |from, page|
  response = client.search(index: index, from: from, size: 2, body: query)
  puts "Total hits: #{response['hits']['total']['value']}" if page.zero?
  puts "Page #{page + 1}:"
  response['hits']['hits'].each { |hit| puts "  #{JSON.generate(hit['_source'])}" }
end

# Search for students who graduated in 2019
puts "\nSearching for students who graduated in 2019......"
query = { query: { range: { gradDate: { gte: '2019-01-01', lte: '2019-12-31' } } } }
response = client.search(index: index, body: query)
puts "Total hits: #{response['hits']['total']['value']}"
response['hits']['hits'].each { |hit| puts "  #{JSON.generate(hit['_source'])}" }

# Update a document
puts "\nUpdating a student's GPA......"
response = client.update(index: index, id: '1', body: { doc: { gpa: 3.92 } })
puts "Result: #{response['result']}, version: #{response['_version']}"

# Get the updated document
response = client.get(index: index, id: '1')
puts "Updated document: #{JSON.generate(response['_source'])}"

# Delete a document
puts "\nDeleting a student......"
response = client.delete(index: index, id: '3', refresh: true)
puts "Result: #{response['result']}"

# Delete the index
puts "\nDeleting the index......"
response = client.indices.delete(index: index)
puts "Acknowledged: #{response['acknowledged']}"

{% include copy.html %}

프로그램의 출력 결과는 다음과 같아요:

Creating index......
Index created: students

Indexing one student......
Result: created, id: 1, version: 1

Indexing many students......
Errors: false
  created id: 2
  created id: 3

Searching for all students......
Total hits: 3
Page 1:
  {"firstName":"Shirley","lastName":"Rodriguez","gpa":3.91,"gradDate":"2019-05-10"}
  {"firstName":"Paulo","lastName":"Santos","gpa":3.93,"gradDate":"2021-05-20"}
Page 2:
  {"firstName":"John","lastName":"Doe","gpa":3.89,"gradDate":"2022-05-15"}

Searching for students who graduated in 2019......
Total hits: 1
  {"firstName":"Shirley","lastName":"Rodriguez","gpa":3.91,"gradDate":"2019-05-10"}

Updating a student's GPA......
Result: updated, version: 2
Updated document: {"firstName":"John","lastName":"Doe","gpa":3.92,"gradDate":"2022-05-15"}

Deleting a student......
Result: deleted

Deleting the index......
Acknowledged: true

Ruby AWS Signature Version 4 클라이언트

opensearch-aws-sigv4 gem은 OpenSearch::Client의 모든 기능을 갖춘 OpenSearch::Aws::Sigv4Client 클래스를 제공해요. 이 두 클라이언트의 유일한 차이점은 OpenSearch::Aws::Sigv4Client가 AWS로 인증하려면 인스턴스화 시점에 Aws::Sigv4::Signer 인스턴스가 필요하다는 것이에요:

require 'opensearch-aws-sigv4'
require 'aws-sigv4'

signer = Aws::Sigv4::Signer.new(service: 'es',
                                region: 'us-east-1',
                                access_key_id: 'key_id',
                                secret_access_key: 'secret',
                                session_token: 'session_token') # required for temporary credentials, such as IAM roles or SSO

client = OpenSearch::Aws::Sigv4Client.new({
    host: 'https://search-<domain-name>-<id>.us-east-1.es.amazonaws.com',
    log: true
}, signer)

client.cluster.health

client.search(index: 'students', q: 'firstName:John')

{% include copy.html %}

관련 문서

더 알아보기 (Learn more)