Tan Kim

elasticsearch

Elasticsearch

분산형 오픈소스 검색 및 분석 엔진. Apache Lucene 기반. REST API로 JSON 문서를 저장·검색·분석. 기본 포트 9200(HTTP), 9300(노드 간 통신).

핵심 개념

개념 설명 RDB 비유
Index 문서들의 논리적 집합 Database / Table
Document JSON 형태의 데이터 단위 Row
Field 문서 내 키-값 쌍 Column
Shard 인덱스를 분할한 단위 (Primary / Replica) Partition
Node 클러스터를 구성하는 단일 서버 -
Cluster 노드들의 집합 -

기본 CRUD

# 문서 생성 (ID 지정)
PUT /products/_doc/1
{
  "name": "노트북",
  "price": 1500000,
  "category": "전자제품"
}
 
# 문서 생성 (ID 자동 생성)
POST /products/_doc
{
  "name": "마우스",
  "price": 30000
}
 
# 문서 조회
GET /products/_doc/1
 
# 문서 수정 (부분 업데이트)
POST /products/_update/1
{
  "doc": {
    "price": 1400000
  }
}
 
# 문서 삭제
DELETE /products/_doc/1
 
# 인덱스 삭제
DELETE /products

검색

기본 검색

# 전체 조회
GET /products/_search
 
# match: 텍스트 분석 후 검색 (풀텍스트)
GET /products/_search
{
  "query": {
    "match": {
      "name": "노트북"
    }
  }
}
 
# term: 정확한 값 검색 (키워드, 숫자, 날짜)
GET /products/_search
{
  "query": {
    "term": {
      "category.keyword": "전자제품"
    }
  }
}
 
# range: 범위 검색
GET /products/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100000,
        "lte": 2000000
      }
    }
  }
}

복합 검색 (bool query)

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "노트북" } }
      ],
      "filter": [
        { "term": { "category.keyword": "전자제품" } },
        { "range": { "price": { "lte": 2000000 } } }
      ],
      "must_not": [
        { "term": { "status": "품절" } }
      ],
      "should": [
        { "match": { "brand": "삼성" } }
      ]
    }
  }
}
설명 점수 영향
must 반드시 일치해야 함 O
filter 반드시 일치, 캐싱 가능 X
must_not 일치하면 제외 X
should 일치하면 점수 가산 O

페이지네이션 및 정렬

GET /products/_search
{
  "query": { "match_all": {} },
  "from": 0,
  "size": 20,
  "sort": [
    { "price": { "order": "asc" } },
    "_score"
  ]
}

Mapping (스키마 정의)

PUT /products
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "nori",          # 한국어 형태소 분석
        "fields": {
          "keyword": { "type": "keyword" }
        }
      },
      "price": { "type": "integer" },
      "category": { "type": "keyword" },
      "created_at": { "type": "date", "format": "yyyy-MM-dd" },
      "tags": { "type": "keyword" }
    }
  }
}

주요 필드 타입

타입 설명
text 풀텍스트 검색용, 형태소 분석 적용
keyword 정확한 값 매칭, 집계, 정렬
integer / long / float 숫자
date 날짜/시간
boolean 불리언
object 중첩 JSON 객체
nested 배열 내 객체를 독립적으로 쿼리할 때

Aggregation (집계)

GET /products/_search
{
  "size": 0,
  "aggs": {
    "by_category": {
      "terms": { "field": "category" }
    },
    "avg_price": {
      "avg": { "field": "price" }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100000 },
          { "from": 100000, "to": 1000000 },
          { "from": 1000000 }
        ]
      }
    }
  }
}

Node.js (@elastic/elasticsearch)

import { Client } from '@elastic/elasticsearch'
 
const client = new Client({ node: 'http://localhost:9200' })
 
// 인덱스 생성
await client.indices.create({
  index: 'products',
  mappings: {
    properties: {
      name: { type: 'text' },
      price: { type: 'integer' },
    },
  },
})
 
// 문서 색인
await client.index({
  index: 'products',
  id: '1',
  document: { name: '노트북', price: 1500000 },
})
 
// 검색
const result = await client.search({
  index: 'products',
  query: {
    bool: {
      must: [{ match: { name: '노트북' } }],
      filter: [{ range: { price: { lte: 2000000 } } }],
    },
  },
  from: 0,
  size: 10,
})
 
const hits = result.hits.hits.map(h => h._source)

한국어 검색 (nori 플러그인)

# 플러그인 설치 (ES 서버에서)
bin/elasticsearch-plugin install analysis-nori
 
# nori analyzer 설정
PUT /products
{
  "settings": {
    "analysis": {
      "analyzer": {
        "nori_analyzer": {
          "type": "nori",
          "decompound_mode": "mixed"   # none | discard | mixed
        }
      }
    }
  }
}
 
# 분석 테스트
GET /products/_analyze
{
  "analyzer": "nori_analyzer",
  "text": "삼성전자 노트북"
}

Docker 실행

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data
 
  kibana:
    image: docker.elastic.co/kibana/kibana:8.13.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
 
volumes:
  es_data:

메모