Elasticsearch 치트시트: 필수 명령어 및 팁

검색, 인덱싱 및 분석을 위한 Elasticsearch 명령어

Page content

Elasticsearch 은 Apache Lucene 를 기반으로 구축된 강력한 분산 검색 및 분석 엔진입니다. 이 종합적인 치트시트는 Elasticsearch 클러스터 작업을 위한 필수 명령어, 모범 사례 및 빠른 참고 자료를 다룹니다.

elasticsearch

참고: 이 가이드의 대부분의 예시는 HTTP 요청에 cURL 을 사용합니다. cURL 을 처음 사용하거나 고급 옵션에 대한 빠른 참조가 필요하시다면, 자세한 명령줄 HTTP 요청 기법을 위한 cURL 치트시트 를 확인하세요.

클러스터 관리

클러스터 상태 확인

이 섹션의 모든 명령어는 Elasticsearch 의 REST API 와 상호작용하기 위해 cURL 을 사용합니다. 필요에 따라 추가 헤더, 인증 및 기타 옵션으로 이러한 요청을 사용자 정의할 수 있습니다.

# 기본 상태 확인
curl -X GET "localhost:9200/_cluster/health?pretty"

# 샤드 정보를 포함한 상세 클러스터 상태
curl -X GET "localhost:9200/_cluster/health?level=shards&pretty"

# 노드 정보 확인
curl -X GET "localhost:9200/_cat/nodes?v"

# 클러스터 설정 확인
curl -X GET "localhost:9200/_cluster/settings?pretty"

노드 작업

# 모든 노드 목록
curl -X GET "localhost:9200/_cat/nodes?v&h=name,node.role,heap.percent,ram.percent,cpu,load_1m"

# 노드 통계
curl -X GET "localhost:9200/_nodes/stats?pretty"

# 핫 스레드 (문제 해결)
curl -X GET "localhost:9200/_nodes/hot_threads"

인덱스 관리

인덱스 생성 및 삭제

# 인덱스 생성
curl -X PUT "localhost:9200/my_index?pretty"

# 설정 포함하여 인덱스 생성
curl -X PUT "localhost:9200/my_index" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}
'

# 인덱스 삭제
curl -X DELETE "localhost:9200/my_index?pretty"

# 모든 인덱스 목록
curl -X GET "localhost:9200/_cat/indices?v"

# 인덱스 통계
curl -X GET "localhost:9200/my_index/_stats?pretty"

인덱스 매핑

# 매핑 정의
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "price": { "type": "float" },
      "created_at": { "type": "date" },
      "tags": { "type": "keyword" },
      "description": { 
        "type": "text",
        "analyzer": "english"
      }
    }
  }
}
'

# 매핑 조회
curl -X GET "localhost:9200/products/_mapping?pretty"

# 매핑 업데이트 (필드 추가)
curl -X PUT "localhost:9200/products/_mapping" -H 'Content-Type: application/json' -d'
{
  "properties": {
    "category": { "type": "keyword" }
  }
}
'

인덱스 템플릿

# 인덱스 템플릿 생성
curl -X PUT "localhost:9200/_index_template/logs_template" -H 'Content-Type: application/json' -d'
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 1
    },
    "mappings": {
      "properties": {
        "timestamp": { "type": "date" },
        "message": { "type": "text" },
        "level": { "type": "keyword" }
      }
    }
  }
}
'

# 템플릿 목록
curl -X GET "localhost:9200/_index_template?pretty"

문서 작업 (CRUD)

문서 생성

# 자동 생성된 ID 로 문서 인덱싱
curl -X POST "localhost:9200/products/_doc?pretty" -H 'Content-Type: application/json' -d'
{
  "name": "Laptop",
  "price": 999.99,
  "tags": ["electronics", "computers"]
}
'

# 특정 ID 로 문서 인덱싱
curl -X PUT "localhost:9200/products/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
  "name": "Laptop",
  "price": 999.99
}
'

# 대량 인덱싱 (Bulk indexing)
curl -X POST "localhost:9200/_bulk?pretty" -H 'Content-Type: application/json' -d'
{ "index": { "_index": "products", "_id": "1" }}
{ "name": "Laptop", "price": 999.99 }
{ "index": { "_index": "products", "_id": "2" }}
{ "name": "Mouse", "price": 29.99 }
'

문서 조회

# ID 로 문서 조회
curl -X GET "localhost:9200/products/_doc/1?pretty"

# 여러 문서 조회
curl -X GET "localhost:9200/_mget?pretty" -H 'Content-Type: application/json' -d'
{
  "docs": [
    { "_index": "products", "_id": "1" },
    { "_index": "products", "_id": "2" }
  ]
}
'

# 문서 존재 여부 확인
curl -I "localhost:9200/products/_doc/1"

문서 업데이트

# 문서 업데이트
curl -X POST "localhost:9200/products/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
  "doc": {
    "price": 899.99
  }
}
'

# 스크립트로 업데이트
curl -X POST "localhost:9200/products/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
  "script": {
    "source": "ctx._source.price *= params.discount",
    "params": {
      "discount": 0.9
    }
  }
}
'

# Upsert (업데이트 또는 삽입)
curl -X POST "localhost:9200/products/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
  "doc": {
    "price": 899.99
  },
  "doc_as_upsert": true
}
'

문서 삭제

# ID 로 삭제
curl -X DELETE "localhost:9200/products/_doc/1?pretty"

# 쿼리로 삭제
curl -X POST "localhost:9200/products/_delete_by_query?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "name": "old"
    }
  }
}
'

검색 쿼리

기본 검색

# 모든 항목 일치
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match_all": {}
  }
}
'

# 일치 (Match) 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "name": "laptop"
    }
  }
}
'

# 멀티 일치 (Multi-match) 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "multi_match": {
      "query": "laptop gaming",
      "fields": ["name", "description"]
    }
  }
}
'

단어 수준 (Term-Level) 쿼리

# Term 쿼리 (정확한 일치)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "term": {
      "tags": "electronics"
    }
  }
}
'

# Terms 쿼리 (여러 값)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "terms": {
      "tags": ["electronics", "computers"]
    }
  }
}
'

# 범위 (Range) 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 1000
      }
    }
  }
}
'

# Exists 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "exists": {
      "field": "description"
    }
  }
}
'

불리언 (Boolean) 쿼리

# Bool 쿼리 (must, should, must_not, filter)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "laptop" }}
      ],
      "filter": [
        { "range": { "price": { "gte": 500 }}}
      ],
      "should": [
        { "term": { "tags": "gaming" }}
      ],
      "must_not": [
        { "term": { "tags": "refurbished" }}
      ]
    }
  }
}
'

고급 검색

# 와일드카드 (Wildcard) 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "wildcard": {
      "name": "lap*"
    }
  }
}
'

# 퍼지 (Fuzzy) 쿼리 (오타 허용)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "laptpo",
        "fuzziness": "AUTO"
      }
    }
  }
}
'

# 접두사 (Prefix) 쿼리
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "prefix": {
      "name": "lap"
    }
  }
}
'

집계 (Aggregations)

메트릭 집계

# 평균, 합계, 최소, 최대
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "avg_price": { "avg": { "field": "price" }},
    "max_price": { "max": { "field": "price" }},
    "min_price": { "min": { "field": "price" }},
    "total_sales": { "sum": { "field": "price" }}
  }
}
'

# Stats 집계
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "price_stats": {
      "stats": { "field": "price" }
    }
  }
}
'

버킷 집계

# Terms 집계 (그룹화)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "popular_tags": {
      "terms": {
        "field": "tags",
        "size": 10
      }
    }
  }
}
'

# Range 집계
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 50 },
          { "from": 50, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}
'

# 날짜 히스토그램
curl -X GET "localhost:9200/logs/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "logs_over_time": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "day"
      }
    }
  }
}
'

중첩 집계 (Nested Aggregations)

# 중첩 집계
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": { "field": "category" },
      "aggs": {
        "avg_price": {
          "avg": { "field": "price" }
        }
      }
    }
  }
}
'

정렬 및 페이지네이션

# 필드별 정렬
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "sort": [
    { "price": { "order": "desc" }},
    { "_score": { "order": "desc" }}
  ]
}
'

# from/size 를 사용한 페이지네이션
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "from": 0,
  "size": 10,
  "query": { "match_all": {} }
}
'

# Search after (심층 페이지네이션용)
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 10,
  "query": { "match_all": {} },
  "sort": [{ "price": "asc" }, { "_id": "asc" }],
  "search_after": [100, "product_123"]
}
'

필드 선택 및 하이라이트

# 특정 필드 선택
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": { "match_all": {} },
  "_source": ["name", "price"]
}
'

# 하이라이트
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": { "description": "gaming laptop" }
  },
  "highlight": {
    "fields": {
      "description": {}
    }
  }
}
'

인덱스 별칭 (Aliases)

# 별칭 생성
curl -X POST "localhost:9200/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
  "actions": [
    { "add": { "index": "products_v1", "alias": "products" }}
  ]
}
'

# 별칭을 새 인덱스로 전환 (다운타임 없음)
curl -X POST "localhost:9200/_aliases?pretty" -H 'Content-Type: application/json' -d'
{
  "actions": [
    { "remove": { "index": "products_v1", "alias": "products" }},
    { "add": { "index": "products_v2", "alias": "products" }}
  ]
}
'

# 별칭 목록
curl -X GET "localhost:9200/_cat/aliases?v"

재인덱싱 (Reindex)

# 한 인덱스부터 다른 인덱스로 재인덱싱
curl -X POST "localhost:9200/_reindex?pretty" -H 'Content-Type: application/json' -d'
{
  "source": {
    "index": "old_products"
  },
  "dest": {
    "index": "new_products"
  }
}
'

# 쿼리를 포함한 재인덱싱
curl -X POST "localhost:9200/_reindex?pretty" -H 'Content-Type: application/json' -d'
{
  "source": {
    "index": "products",
    "query": {
      "range": {
        "price": { "gte": 100 }
      }
    }
  },
  "dest": {
    "index": "expensive_products"
  }
}
'

스냅샷 및 백업

# 스냅샷 저장소 등록
curl -X PUT "localhost:9200/_snapshot/my_backup?pretty" -H 'Content-Type: application/json' -d'
{
  "type": "fs",
  "settings": {
    "location": "/mount/backups/my_backup"
  }
}
'

# 스냅샷 생성
curl -X PUT "localhost:9200/_snapshot/my_backup/snapshot_1?wait_for_completion=true&pretty"

# 스냅샷 복원
curl -X POST "localhost:9200/_snapshot/my_backup/snapshot_1/_restore?pretty"

# 스냅샷 목록
curl -X GET "localhost:9200/_snapshot/my_backup/_all?pretty"

# 스냅샷 삭제
curl -X DELETE "localhost:9200/_snapshot/my_backup/snapshot_1?pretty"

성능 최적화

인덱스 설정

# 대량 인덱싱 중 리프레시 비활성화
curl -X PUT "localhost:9200/products/_settings?pretty" -H 'Content-Type: application/json' -d'
{
  "index": {
    "refresh_interval": "-1"
  }
}
'

# 대량 인덱싱 후 재활성화
curl -X PUT "localhost:9200/products/_settings?pretty" -H 'Content-Type: application/json' -d'
{
  "index": {
    "refresh_interval": "1s"
  }
}
'

# 강제 병합 (최적화)
curl -X POST "localhost:9200/products/_forcemerge?max_num_segments=1&pretty"

캐시 정리

# 모든 캐시 정리
curl -X POST "localhost:9200/_cache/clear?pretty"

# 특정 캐시 정리
curl -X POST "localhost:9200/products/_cache/clear?query=true&pretty"

모니터링 및 문제 해결

# 대기 중인 작업
curl -X GET "localhost:9200/_cat/pending_tasks?v"

# 스레드 풀 통계
curl -X GET "localhost:9200/_cat/thread_pool?v"

# 세그먼트 정보
curl -X GET "localhost:9200/_cat/segments?v"

# 복구 정보
curl -X GET "localhost:9200/_cat/recovery?v&h=index,stage,time"

# Tasks API
curl -X GET "localhost:9200/_tasks?detailed=true&pretty"

Python 클라이언트 예제

from elasticsearch import Elasticsearch

# Elasticsearch 연결
es = Elasticsearch(['http://localhost:9200'])

# 문서 인덱싱
doc = {
    'name': 'Laptop',
    'price': 999.99,
    'tags': ['electronics']
}
es.index(index='products', id=1, document=doc)

# 검색
resp = es.search(index='products', query={'match': {'name': 'laptop'}})
for hit in resp['hits']['hits']:
    print(hit['_source'])

# 대량 인덱싱
from elasticsearch.helpers import bulk

actions = [
    {
        '_index': 'products',
        '_id': i,
        '_source': {'name': f'Product {i}', 'price': i * 10}
    }
    for i in range(1000)
]
bulk(es, actions)

JavaScript/Node.js 클라이언트 예제

Elasticsearch JavaScript 클라이언트는 클러스터와 상호작용하는 타입 안전한 방식을 제공합니다. 프로덕션 애플리케이션의 경우 더 나은 타입 안전성과 자동 완성 기능을 위해 TypeScript 사용을 고려해 보세요. 타입 정의 및 인터페이스에 대한 모범 사례는 TypeScript 치트시트 를 참조하세요.

const { Client } = require('@elastic/elasticsearch');
const client = new Client({ node: 'http://localhost:9200' });

// 문서 인덱싱
async function indexDoc() {
  await client.index({
    index: 'products',
    id: 1,
    document: {
      name: 'Laptop',
      price: 999.99
    }
  });
}

// 검색
async function search() {
  const result = await client.search({
    index: 'products',
    query: {
      match: { name: 'laptop' }
    }
  });
  console.log(result.hits.hits);
}

// 대량 인덱싱
async function bulkIndex() {
  const operations = [];
  for (let i = 0; i < 1000; i++) {
    operations.push({ index: { _index: 'products', _id: i } });
    operations.push({ name: `Product ${i}`, price: i * 10 });
  }
  await client.bulk({ operations });
}

강력한 타이핑을 사용한 TypeScript 예제

import { Client } from '@elastic/elasticsearch';

interface Product {
  name: string;
  price: number;
  tags?: string[];
  created_at?: Date;
}

const client = new Client({ node: 'http://localhost:9200' });

async function indexProduct(product: Product, id: number): Promise<void> {
  await client.index<Product>({
    index: 'products',
    id: id.toString(),
    document: product
  });
}

async function searchProducts(query: string): Promise<Product[]> {
  const result = await client.search<Product>({
    index: 'products',
    query: {
      match: { name: query }
    }
  });
  
  return result.hits.hits.map(hit => hit._source as Product);
}

모범 사례

인덱스 설계

  • 최적의 성능을 위해 샤드 크기를 20-50GB 사이로 유지하세요
  • 시계열 데이터에는 인덱스 수명 주기 관리 (ILM) 를 사용하세요
  • 데이터 인덱싱 전에 매핑을 신중하게 설계하세요
  • 적절한 필드 유형 (keyword vs text, 날짜 형식) 을 사용하세요
  • 필요하지 않다면 대용량 문서에 대해 _source 를 비활성화하세요

쿼리 최적화

  • 점수가 필요하지 않을 경우 쿼리 대신 필터를 사용하세요
  • 구조화된 데이터에는 단어 수준 (term-level) 쿼리를 선호하세요
  • 여러 조건을 효율적으로 결합하기 위해 bool 쿼리를 사용하세요
  • 심층 페이지네이션을 위해 search_after 를 사용하여 페이지네이션을 구현하세요
  • 자주 사용되는 필터를 캐시하세요

인덱싱 성능

  • 배치 인덱싱에는 대량 API 를 사용하세요 (요청당 1000-5000 개 문서)
  • 대량 작업 중에는 리프레시를 비활성화하세요
  • 무거운 인덱싱 도중 index.refresh_interval 을 늘리세요
  • 병렬 인덱싱을 위해 여러 스레드/워커를 사용하세요
  • 더 나은 샤드 분포를 위해 라우팅 사용을 고려하세요

클러스터 관리

  • 클러스터 상태를 정기적으로 모니터링하세요
  • 적절한 복제본 구성을 설정하세요
  • 대규모 클러스터에는 전용 마스터 노드를 사용하세요
  • 스냅샷을 사용하여 적절한 백업 전략을 구현하세요
  • JVM 힙 사용량을 모니터링하세요 (75% 미만 유지)

보안

  • 인증 및 권한 부여를 활성화하세요 (X-Pack Security)
  • 프로덕션 배포에는 HTTPS 를 사용하세요 (SSL/TLS 를 위해 cURL 에 --cacert, --cert, --key 옵션을 구성)
  • 적절한 역할 기반 접근 제어 (RBAC) 를 구현하세요
  • 정기적인 보안 업데이트 및 패치 적용
  • 저장 및 전송 중 데이터 암호화

일반적인 사용 사례

전체 텍스트 검색

Elasticsearch 는 다음과 같은 기능을 갖춘 전체 텍스트 검색에 탁월합니다:

  • 관련성 점수 (Relevance scoring)
  • 퍼지 매칭 (Fuzzy matching)
  • 구문 매칭 (Phrase matching)
  • 동의어 처리
  • 다국어 지원 검색을 Postgres 내부에 유지할지 전용 검색 엔진으로 이동할지 평가 중이라면, 이 PostgreSQL 전체 텍스트 검색 vs Elasticsearch 비교 에서 실용적인 트레이드오프를 확인할 수 있습니다.

로그 분석 (ELK Stack)

  • Logstash/Filebeat 로 로그 수집
  • Elasticsearch 에서 로그 인덱싱 및 검색
  • Kibana 대시보드에서 시각화
  • 이상 징후를 위한 알림 설정

이커머스 검색

  • 제품 카탈로그 검색
  • 집계를 활용한 Faceted 네비게이션
  • 자동 완성 및 제안 기능
  • 개인화된 검색 결과

애플리케이션 성능 모니터링

  • 애플리케이션 메트릭 인덱싱
  • 실시간 모니터링 대시보드
  • 이상 징후 탐지
  • 성능 추이 분석

유용한 링크

공식 Elasticsearch 리소스

관련 치트시트 및 가이드

구독하기

시스템, 인프라, AI 엔지니어링에 관한 새 글을 받아보세요.