Skip to content

ElasticSearch ‐ All About Search

woojin edited this page Aug 1, 2026 · 2 revisions

검색하는 두 가지 방법

  • URI SearchesHTTP 요청의 URL 안에서 직접 검색하는 간단한 방법이다. DSL 질의보다 제한적이지만, 빠르고 단순한 검색에 편리하다.
  • DSL (Domain Specific Language) Queries — URI 검색보다 강력하고 유연하다. Elasticsearch의 query DSL은 복잡한 질의를 만들 수 있는 JSON 기반 언어이며, 전문 검색, boolean 질의, 필터링, 집계 등을 지원한다.

URI Search

# 문서 색인
POST /my_index/_doc/
{
  "content": "quick book",
  "publish_date": "2024-02-01"
}

POST /my_index/_doc/
{
  "content": "quick walk",
  "publish_date": "2024-03-01"
}

# URI Search
GET /my_index/_search?q=content:quick
GET /my_index/_search?q=content:quick AND content:walk
  • q 파라미터에 필드:값 형태로 조건을 적는다.
  • AND 같은 연산자도 쓸 수 있다.

DSL Queries

  • Basic Searchcontent 필드에 "quick"이 담긴 문서를 찾는다.
GET /my_index/_search
{
  "query": {
    "match": {
      "content": "quick"
    }
  }
}
  • Full-Text Search with Query Stringquery_string 질의로 여러 필드에 걸친 전문 검색을 고급 문법과 함께 수행한다.
GET /my_index/_search
{
  "query": {
    "query_string": {
      "query": "quick"
    }
  }
}
GET /my_index/_search
{
  "query": {
    "query_string": {
      "query": "quick OR brown",
      "fields": ["content"]
    }
  }
}
  • Boolean Queriesmust, should, must_not 같은 논리 연산자로 여러 질의를 조합한다.
GET /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "content": "quick" } },
        { "match": { "content": "walk" } }
      ]
    }
  }
}
  • Filtering Results — 정형 조건으로 문서를 걸러낼 때 쓴다. 연관도 스코어에 영향을 주지 않는다.
GET /my_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": { "content": "quick" }
      }
    }
  }
}
  • Range Queries — 필드 값이 특정 범위 안에 드는 문서를 찾는다. (숫자 범위, 날짜 범위 등)
GET /my_index/_search
{
  "query": {
    "range": {
      "publish_date": {
        "gte": "2024-01-01",
        "lte": "2024-12-31"
      }
    }
  }
}
  • Phrase Search단어의 정확한 순서를 찾는다.
GET /my_index/_search
{
  "query": {
    "match_phrase": {
      "content": "quick brown fox"
    }
  }
}
  • Wildcard Search*(임의의 문자열)?(임의의 한 문자) 패턴으로 검색한다.
GET /my_index/_search
{
  "query": {
    "wildcard": {
      "content": "qui*"
    }
  }
}

Term-level Query란?

  • Elasticsearch의 term-level query는 숫자, 날짜, keyword 같은 정형 데이터에서 정확히 일치하는 값을 찾는 질의다.
  • 텍스트를 분석(analyze)하고 토큰화(tokenize)하는 전문 검색 질의와 달리, term-level query는 색인에서 그 값 그대로를 찾는다.
  • 주로 keyword, numeric, date, boolean 같은 분석되지 않는 필드에 쓴다.

주요 특징

  • Exact Matches — 입력 텍스트를 분석하지 않는다. 지정한 필드에서 정확히 일치하는 값을 찾는다.
  • Non-Analyzed Fields — 분석되지 않는 필드(keyword, integer, date 등)에 주로 쓴다.
  • Case Sensitivity — keyword 필드에 쓸 때는 대소문자를 구분한다.

대표 질의

term Query — 값 하나와 정확히 일치하는 문서를 찾는다.

GET /my_index/_search
{
  "query": {
    "term": {
      "status": "published"
    }
  }
}

terms Query — 나열한 값들 중 하나라도 일치하는 문서를 찾는다.

GET /my_index/_search
{
  "query": {
    "terms": {
      "status": ["published", "draft"]
    }
  }
}

Prefix Search

  • prefix search는 필드 값이 지정한 접두사로 시작하는 문서를 찾는 term-level query다.
  • 특정 문자로 시작하는 term을 찾고 싶은데 전체 값을 모를 때 특히 유용하다.
GET /my_index/_search
{
  "query": {
    "prefix": {
      "product_name": "iph"
    }
  }
}

Wildcard Search

  • wildcard search는 와일드카드 문자를 써서 필드 값이 지정한 패턴에 맞는 문서를 찾는 term-level query다.
  • * (asterisk)임의의 문자열과 일치한다. 빈 문자열도 포함한다.
  • ? (question mark)임의의 한 문자와 일치한다.
GET /my_index/_search
{
  "query": {
    "wildcard": {
      "product_name": "iph*ne"
    }
  }
}

Regular Expression Search

  • regex search는 필드 값이 지정한 정규식에 맞는 문서를 찾는 강력한 term-level query다.
패턴 의미
. 임의의 한 문자
.* 임의의 문자열 (없어도 됨)
[abc] a, b, c 중 한 글자
[a-z] a부터 z까지 소문자 중 한 글자
[0-9] 0부터 9까지 숫자 중 하나
GET /my_index/_search
{
  "query": {
    "regexp": {
      "product_name": "iph[o0]ne.*"
    }
  }
}

Full Text Search란?

  • full-text search는 문서, 이메일, 기사, 웹 페이지 같은 대량의 텍스트 데이터를 훑어 내용 기반으로 연관된 정보를 찾는 강력한 검색 기법이다.
  • 정확한 일치를 찾는 term-level query와 달리, full-text search 질의는 텍스트를 분석해 더 유연하고 연관성 높은 결과를 제공한다.

동작 방식

  • Text Analysis — 토큰화, 소문자 변환, stemming, stop word 제거
  • Inverted Index — 빠른 검색을 위해 term을 저장한다
  • Search Query — 검색 질의가 수행되면 Elasticsearch는 질의 텍스트도 같은 방식으로 분석한 뒤, 역색인에서 일치하는 term을 찾는다
  • Relevance Scoringterm frequency, document frequency, field length 같은 요소를 바탕으로 문서마다 연관도 점수를 계산한다

Relevance Scoring

  • Elasticsearch는 연관도 점수 계산에 TF-IDF(Term Frequency-Inverse Document Frequency) 모델, BM25, 또는 이들의 조합을 사용한다.
  • Term Frequency (TF)
    • 한 문서 안에서 그 term이 얼마나 자주 등장하는지를 잰다.
    • 빈도가 높을수록 연관도가 올라간다.
  • Inverse Document Frequency (IDF)
    • 인덱스 전체에서 그 term이 얼마나 희귀한지를 잰다.
    • 드문 term일수록 연관도 점수에 더 크게 기여한다.
  • Field Length Norm
    • 짧은 필드일수록 더 연관성이 높은 경향이 있으므로, 필드 길이가 짧으면 연관도 점수가 올라간다.

Leaf Queries

  • leaf query는 Elasticsearch에서 가장 단순한 형태의 질의다.
  • 특정 필드에서 특정 값을 검색하는 역할을 한다.
    • 이 질의들은 안에 다른 질의를 담지 않는다.
    • 조건을 데이터와 직접 대조한다.
  • Field-Level Operations — 문서의 특정 필드에 직접 작용한다.
  • Single Condition — term 일치, 범위, 존재 여부 같은 단일 조건 하나를 평가한다.
  • No Nesting — leaf query는 다른 질의를 담거나 조합하지 않는다.
GET /my_index/_search
{
  "query": {
    "term": { "status": "published" }
  }
}
GET /my_index/_search
{
  "query": {
    "match": { "content": "Elasticsearch" }
  }
}
GET /my_index/_search
{
  "query": {
    "range": {
      "publish_date": {
        "gte": "2023-01-01",
        "lte": "2023-12-31"
      }
    }
  }
}

Compound Queries

  • compound query는 여러 질의를 조합하는 상위 레벨 질의다. 조합 대상은 leaf query일 수도, 다른 compound query일 수도 있다.
  • 다른 질의를 조합·변형하거나 실행을 제어해서 더 복잡하고 정교한 검색 로직을 구성하는 데 쓴다.
  • Combining Queries — AND, OR, NOT 같은 논리 연산자로 여러 질의를 조합한다.
  • Nesting — compound query 안에 다른 compound query나 leaf query를 담을 수 있어, 깊고 복잡한 검색 구조를 만들 수 있다.
  • Execution Control하위 질의가 어떻게 실행되고 결과가 어떻게 결합될지(스코어링, 필터링 등)를 제어할 수 있다.

Bool Query

  • bool query는 가장 강력하고 다재다능한 compound query 중 하나다.
  • SQL이 AND, OR, NOT을 쓰는 것과 매우 비슷하게 논리 연산자로 여러 질의를 조합할 수 있다.
  • SQL의 WHERE 절에 들어가는 boolean 로직에 해당한다고 생각하면 된다.
SQL 대응 의미
must AND must 절의 모든 질의가 일치해야 문서가 매치로 간주된다
must_not NOT must_not 절의 질의가 일치하지 않아야 매치로 간주된다
should OR 일치하면 연관도 점수가 올라간다. must나 filter 절이 없으면 should 중 최소 하나는 일치해야 한다
filter AND must와 비슷하지만 연관도 점수에 기여하지 않는다. true/false 같은 이진 조건으로 문서를 걸러낼 때 쓴다
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "category": "electronics" } },
        { "term": { "in_stock": true } }
      ]
    }
  }
}

Boosting Query란?

  • boosting query는 특정 질의에 가중치를 주거나, 어떤 조건에 맞는 문서의 연관도를 낮춤(demoting) 으로써 문서의 연관도 점수에 영향을 주는 질의다.
  • Positive Query끌어올리고 싶은(promote·boost) 문서를 매칭한다.
  • Negative Query끌어내리고 싶은(demote) 문서, 즉 연관도를 낮출 문서를 매칭한다.
{
  "query": {
    "boosting": {
      "positive": {
        "match": { "field_name": "positive_value" }
      },
      "negative": {
        "match": { "field_name": "negative_value" }
      },
      "negative_boost": 0.5
    }
  }
}
  • positive : 결과에 포함될 문서를 정한다.
  • negative : 점수를 깎을 문서를 정한다.
  • negative_boost — negative 질의에 일치하는 문서의 점수를 얼마나 줄일지 계수를 지정한다. 0과 1 사이의 값이 일반적이며, 0이면 결과에서 완전히 제거되고 1이면 감점이 없다.

Disjunction Max Query란?

  • Disjunction Max Query(줄여서 dis_max query)는 여러 질의를 실행하되, 일치한 질의들의 점수를 합치는 대신 가장 잘 맞은 질의 하나의 점수를 돌려주는 compound query다.

동작 방식

  • Best Score Winsdis_max하위 질의 중 가장 높은 점수를 골라 그 문서의 연관도 점수로 삼는다. 이렇게 하면 문서가 여러 필드나 조건에 걸릴 때 점수가 부풀려지는 것을 막는다.
  • Tie Breaking — 선택 파라미터인 tie_breaker최고점이 아닌 하위 질의들의 점수 일부를 더할 수 있다. 여러 필드·조건에 걸린 문서를 약간 밀어 올리되, 여전히 최고 매치를 우선하게 한다.

multi_match와의 비교

  • multi_match Query — 여러 필드의 점수를 모드(best_fields, most_fields 등)에 따라 결합한다. 점수를 합하거나 평균 내고 싶은 다중 필드 검색에 주로 쓴다.
  • dis_max Query하위 질의 중 최고 점수에 집중한다. 최고 매치를 강조하고 싶을 때 쓰며, 보조 매치를 위한 tie breaker를 선택적으로 둘 수 있다.
GET /products/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "name": "Speaker" } },
        { "match": { "category": "electronics" } }
      ],
      "tie_breaker": 0.3
    }
  }
}
  • 문서 A — name 점수 8.0, category 점수 5.0
  • dis_max (tie_breaker 없음) → 8.0
  • dis_max (tie_breaker 0.3) → 8.0 + (5.0 × 0.3) = 9.5
  • bool.should (점수 합산) → 8.0 + 5.0 = 13.0
  • tie_breaker0이면 최고점만 쓴다. 1이면 전부 합산해 bool.should와 같아진다.

Nested Query란

  • nested query는 nested object 안의 필드를 질의할 때 쓴다. nested object는 배열 안의 각 객체를 별개의 개체로 다뤄야 하는 경우를 위한 특별한 object 타입이다.
  • nested query는 중첩 문서와 부모 문서의 관계를 유지한 채로 그 안을 검색할 수 있게 해준다.
  • 일반 object 필드의 문제
    • 일반 object 필드를 쓰면 Elasticsearch는 객체들을 평탄화(flatten)하면서 필드 간의 관계를 잃어버린다.
    • 예를 들어 nameprice 필드를 가진 상품 배열이 있을 때, 일반 질의는 한 상품의 name과 다른 상품의 price를 같은 문서 안에서 잘못 매칭할 수 있다.
  • 해결책
    • Nested Object — 필드를 nested로 정의하면 배열의 각 객체가 별개의 숨겨진 문서로 색인된다. 이 문서들은 부모 문서와 연결되어 있다.
    • Nested Query — nested query로 이 중첩 문서들 안을 검색하되, 부모 문서와의 관계를 유지한다.

평탄화가 무엇을 망가뜨리는가?

{
  "products": [
    { "name": "Speaker", "price": 100 },
    { "name": "Cable",   "price": 10  }
  ]
}
  • 위의 문서가 일반 object로 색인되면 다음과 같아진다.
products.name  : ["Speaker", "Cable"]
products.price : [100, 10]
  • 두 배열이 따로 저장되어 짝이 사라진다.(즉, 인덱스에 의한 암묵적 의존이 되어버림)
  • 그래서 "Speaker이면서 price가 10" 을 검색하면 없는 조합인데도 걸린다. name 배열에 Speaker가 있고 price 배열에 10이 있기 때문이다.
  • 그래서 nested로 색인되면 다음과 같아진다.
{ name: "Speaker", price: 100 }
{ name: "Cable",   price: 10  }

사용 예시

매핑

PUT /orders
{
  "mappings": {
    "properties": {
      "products": {
        "type": "nested",
        "properties": {
          "name":  { "type": "keyword" },
          "price": { "type": "integer" }
        }
      }
    }
  }
}

질의

GET /orders/_search
{
  "query": {
    "nested": {
      "path": "products",
      "query": {
        "bool": {
          "must": [
            { "term":  { "products.name": "Speaker" } },
            { "range": { "products.price": { "lte": 50 } } }
          ]
        }
      }
    }
  }
}
  • path로 어느 nested 필드 안에서 찾을지 지정한다.
  • query 안의 조건들은 같은 숨겨진 문서 하나에서 모두 만족해야 한다.

Inner Hits란

  • Elasticsearch에서 inner hits는 nested query 안에서 중첩 문서나 개별 hit의 상세 정보를 함께 돌려받는 기능이다.
  • nested query를 쓸 때는 어떤 부모 문서가 걸렸는지뿐 아니라, 그 부모를 매치시킨 구체적인 중첩 문서(하위 문서)가 무엇인지도 궁금할 수 있다. inner hits가 그것을 알려준다.

왜 필요한가?

  • nested query의 기본 응답은 부모 문서 전체다. 배열에 원소가 여러 개일 때 그중 어느 것이 조건에 맞았는지는 응답에 나오지 않는다.
  • 예를 들어, 질의 products 중 name="Speaker" 이고 price <= 150 인 것을 추출한다고 가정하면 다음과 같이 기본 응답이 나오게 된다.
{
  "_source": {
    "products": [
      { "name": "Speaker", "price": 100 },   ← 이것 때문에 걸렸는데
      { "name": "Cable",   "price": 10  },
      { "name": "Speaker", "price": 300 }    ← 이건 조건에 안 맞는데
    ]
  }
}
  • 애플리케이션에서 다시 걸러내면 되지만, 질의 조건을 코드에 한 번 더 구현해야 하고 두 곳이 어긋날 수 있다.
GET /orders/_search
{
  "query": {
    "nested": {
      "path": "products",
      "query": {
        "bool": {
          "must": [
            { "term":  { "products.name": "Speaker" } },
            { "range": { "products.price": { "lte": 150 } } }
          ]
        }
      },
      "inner_hits": {}
    }
  }
}
"hits": [
  {
    "_source": { "products": [ … 전체 배열 … ] },
    "inner_hits": {
      "products": {
        "hits": {
          "hits": [
            {
              "_nested": { "field": "products", "offset": 0 },
              "_score": 1.8,
              "_source": { "name": "Speaker", "price": 100 }
            }
          ]
        }
      }
    }
  }
]
  • _nested.offset : 배열에서 몇 번째 원소가 걸렸는지 알려준다.
  • _score : 그 중첩 문서 자체의 점수다.
  • 조건에 맞은 원소만 담기므로, 애플리케이션이 다시 걸러낼 필요가 없다.

📖 Java🔥

📖 Kotlin⭐

📖 Coroutine📎

📖 Spring🔥

📖 Spring Security⭐

📖 Spring Security OAuth2⭐

📖 Spring Batch📎

📖 Database🔥

📖 MySQL🔥

📖 Redis⭐

📖 JPA⭐

📖 QueryDsl📎

📖 MSA⭐

📖 Kafka⭐

📖 Apache Flink📎

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP🔥

📖 AWS⭐

📖 Docker⭐

📖 Kubernetes⭐

📖 Github Actions📎

📖 Jenkins📎

📖 Nginx⭐

📖 Monitoring📎

📖 Test(feat. Load Testing)📎

📖 Test(feat. Java)⭐

📖 Spring AI📎

📖 gRPC📎

  • [gRPC - Writing .proto Files with Protocol Buffers]
  • [gRPC - Various Communication Patterns in gRPC]
  • [gRPC - gRPC Optimization Techniques and Advanced Features]

📖 TDD(Test-Driven-Development)⭐

📖 PostgreSQL📎

  • [PostgreSQL - Docker만을 사용하는 경량화된 환경 구성 방법]
  • [PostgreSQL - PostgreSQL에서 제공하는 데이터 타입]
  • [PostgreSQL - PostgreSQI의 JSONB, 역인덱싱과 활용 방법]
  • [PostgreSQL - 데이터베이스 성능을 위한 최적화 패턴 및 전략]
  • [PostgreSQL - 트랜잭션과 ACID, Isolation 수준별 차이]
  • [PostgreSQL - Database Lock 교착상태와 읽기/쓰기 성능을 보장하는 MVCC 모델]
  • [PostgreSQL - pgvector와 벡터 저장, 유사도 검색 패턴 개념]
  • [PostgreSQL - 벡터 인덱스 최적화와 벡터 검색과 전문 검색 결합 패턴]
  • [PostgreSQL - PostgreSQL 플러그인]
  • [PostgreSQL - PostGIS - 공간 쿼리와 GIST 인덱스, 지리 타입과 공간 쿼리를 위한 타입과 기본 함수]
  • [PostgreSQL - pg_search - 검색 엔진 없이 텍스트 검색 구현과 주의사항]
  • [PostgreSQL - 단일 인스턴스 한계를 극복하는 분산 패턴과 스케줄링, 분산 환경 구축 방법]
  • [PostgreSQL - Citus - 분산 테이블과 분산 쿼리를 위한 Extension과 데이터 분산 처리]
  • [PostgreSQL - pg_cron - PostgreSQL로 구성하는 CronJob]
  • [PostgreSQL - 스케줄러 + 분산 처리를 동시에 도입하는 주기적 집계 쿼리 패턴]

📖 Workflow-Driven Techniques for Large-Scale Traffic Processing📎

  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka + Debezium을 활용한 CDC 패턴 설계]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Temporal을 활용한 워크플로우 패턴]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Docker와 경량 이미지를 활용한 환경 구축 방법]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka에서의 메시지 Delivery Guarantee]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - 실시간 동기화의 핵심 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - MySQL Binary Log 기반의 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Binary Log 기반의 CDC 구현 플랫폼 Debezium이란?]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture Best Practice와 주의사항]

📖 Reactive Programming📎

📖 ElasticSearch📎

📖 Design Pattern📎

📖 Clean Spring📎

  • [Clean Spring - Domain-Driven Development]
  • [Clean Spring - Domain-Driven Development with Design Patterns]
  • [Clean Spring - Developing Membership Application with Hexagonal Architecture]
  • [Clean Spring - JPA and Domain Model Patterns]
  • [Clean Spring - Designing a Consistent Domain Model with Aggregates]
  • [Clean Spring - Web API Adapter]
  • [Clean Spring - Hexagonal Architecture: Ports]
  • [Clean Spring - Hexagonal Architecture: Application Components]
  • [Clean Spring - Test Improvement & Architecture Validation]
  • [Clean Spring - Developing Application Components]
  • [Real MySQL 8.0 - 인덱스]
  • [Real MySQL 8.0 - 실행 계획]
  • [Real MySQL 8.0 - 아키텍처]
  • [Real MySQL 8.0 - 트랜잭션과 잠금]

Clone this wiki locally