← 학습 카테고리

Learn

Elasticsearch

50개 모듈 · 현재 15번째

Elasticsearch 모듈 15/50 elasticsearch-learn-15

Painless script_score — 커스텀 스코어링과 성능 주의점

Elasticsearch Painless script_score 및 Downsampling 공식 문서 종합 (웹 리서치, 2026) — Elastic 공식 문서(query-dsl-script-score-query, painless-score-context, manage-data/lifecycle/rollup/migrating-from-rollup-to-downsampling) Elastic 공식 문서 Script score query 및 Painless Score context

이 모듈을 다 읽으면

  • script_score가 내부 쿼리의 스코어를 어떻게 대체하는지 설명할 수 있다
  • min_score가 스크립트 계산 이전이 아니라 이후에 적용된다는 함정을 설명할 수 있다
  • script_score를 언제 피해야 하는지(대안 포함) 판단할 수 있다

elasticsearch-learn-05에서 다룬 bool 쿼리로 후보 문서를 걸러낸 뒤, 그 결과의 점수 자체를 커스텀 로직으로 재계산하고 싶을 때 쓰는 script_score 쿼리와 이를 구현하는 Painless 스크립팅 언어의 핵심, 그리고 무심코 쓰면 성능이 무너지는 함정을 정리한다.

script_score — 내부 쿼리는 '후보를 거르고', 스크립트는 '점수만 다시 매긴다'

script_score 쿼리는 내부(inner) 쿼리를 감싸는 구조다 — 내부 쿼리가 먼저 매칭되는 문서 후보를 찾아내고, Painless 스크립트가 그 후보 문서들 각각에 대해 독립적으로 새 점수를 계산해 원래 점수를 완전히 대체한다. 즉 '어떤 문서가 결과에 포함될지'는 내부 쿼리가 결정하고, '그 문서들의 순위를 어떻게 매길지'는 스크립트가 결정하는 역할 분리 구조다. 필드 값 접근은 doc['필드명'].value 문법을 쓰며(예: 평점 필드를 10으로 나누는 doc['rating'].value / 10), 문서에 해당 필드가 아예 없을 수도 있는 경우 doc['필드명'].size() == 0으로 사전에 확인해야 스크립트 실행 중 에러 없이 안전하게 처리할 수 있다.

핵심 포인트

  • script_score는 내부 쿼리를 감싸 '후보 선정은 내부 쿼리, 점수 재계산은 스크립트'로 역할을 분리한다
  • 필드 값은 doc['필드명'].value로 접근, 필드 부재 가능성은 doc['필드명'].size() == 0으로 사전 확인

min_score의 함정 — 필터링은 스크립트 계산 '이후'에 일어난다

min_score 파라미터로 특정 점수 미만 문서를 결과에서 제외할 수 있는데, 이 컷오프는 내부 쿼리의 원래 점수가 아니라 스크립트가 재계산을 마친 '최종 점수'를 기준으로 적용된다. 즉 내부 쿼리 단계에서는 아주 낮은 원점수의 문서라도 일단 후보로 남아 스크립트 계산까지 전부 거친 뒤에야 min_score로 걸러진다 — 이는 '애초에 관련도가 낮은 문서는 스크립트 계산 자체를 건너뛰어 비용을 아끼겠다'는 기대와 어긋나는 지점이라 흔히 오해되는 함정이다.

핵심 포인트

  • min_score는 스크립트로 재계산된 '최종 점수'에 적용됨 — 내부 쿼리의 원점수 단계에서 미리 걸러지는 게 아님
  • 관련도 낮은 후보도 스크립트 계산 비용은 동일하게 발생한 뒤에야 min_score로 제외된다는 점을 성능 설계 시 감안해야 함

성능 함정 — 매칭되는 문서 수만큼 스크립트가 매번 실행된다

script_score는 내부 쿼리에 매칭된 '모든' 문서마다 스크립트를 실행해 점수를 계산하므로, match_all처럼 매칭 범위가 매우 넓은 쿼리에 무거운 계산(특히 벡터 유사도 함수 등)을 얹으면 문서 수에 비례해 비용이 커진다. 공식 가이드는 (1) 벡터 함수를 쓸 때는 query 파라미터로 매칭 문서 수 자체를 먼저 좁힐 것, (2) 정적인 필드 부스트나 날짜/지리적 근접도 기반 부스트처럼 단순한 경우는 script_score 대신 더 가벼운 전용 쿼리(rank_feature, distance_feature)를 쓸 것을 권장한다 — 커스텀 스크립트는 이런 전용 쿼리로 표현이 안 되는 진짜 커스텀 로직에만 쓰는 최후 수단으로 취급하는 게 맞다.

핵심 포인트

  • script_score는 매칭된 문서 수만큼 매번 스크립트를 실행 — match_all + 무거운 스크립트는 성능 함정
  • 단순 필드/거리 기반 부스트는 rank_feature/distance_feature 같은 전용 쿼리가 더 가볍다 — script_score는 진짜 커스텀 로직에만 최후 수단으로 사용