본문 바로가기

Framework/Spring

[Spring] Elasticsearch에서 스크립팅 언어로 조건 필터링 구현하기(feat. Java) - 컴도리돌이

728x90

Elasticsearch에서 데이터 조회 시 특정 조건에 따라 필드 값을 조정하고, 오늘 날짜와 비교해 필터링하는 작업은 종종 필요한 기능입니다. 단순한 필터링 쿼리만으로는 표현하기 어렵거나 우아하지 못한 경우가 많은데요, 저 역시 필드 값의 유효성을 확인하고 이를 기준으로 조건을 처리하면서 우아하지 않은 쿼리식 때문에 고생했죠. 😅


그러다 마침내 스크립팅 언어인 Painless를 사용해 원하는 조건을 충족할 수 있었습니다. 이번 포스팅에서는 스크립트 사용법과 함께 이러한 과정을 공유해 보겠습니다.


Painless 스크립트 예제

Elasticsearch에서 다양한 스크립트 옵션을 사용하는 예제를 살펴보겠습니다. inline, params, lang과 같은 다양한 옵션을 포함해 작성한 예제입니다. 특히, Painless는 Elasticsearch의 성능과 보안을 극대화하기 위해 설계된 스크립팅 언어로, Elasticsearch 내에서 거의 모든 스크립트가 안전하게 실행될 수 있도록 도와줍니다.

// 조건 필터링 스크립트 예제 with 다양한 옵션
b.should(sh -> sh.script(
    sco -> sco.script(sco2 -> sco2
        .inline(
            i -> i.lang("painless") // 스크립트 언어 설정
                  .source(
                      """
                      // 필드 값이 특정 조건을 만족하는지 확인
                      def fieldValue = doc['field_a.keyword'].size() > 0 ? Integer.parseInt(doc['field_a.keyword'].value) : 0;
                      return fieldValue > params.threshold;
                      """
                  )
                  .params(Map.of("threshold", JsonData.of(today))) // 외부 파라미터 설정
        )
        .id("custom_script_id") // 스크립트 ID
        .lang("painless") // 다시 명시된 스크립트 언어
    ))
));

1. inline

inline 옵션은 스크립트 내용을 즉석에서 작성하고 실행할 때 사용하는 방식입니다. 예를 들어, 필드의 값이 특정 값보다 큰지를 바로 확인하고 싶다면 다음과 같이 inline 스크립트를 작성할 수 있습니다.

b.should(sh -> sh.script(
    sco -> sco.script(sco2 -> sco2
        .inline(
            i -> i.lang("painless") 
                  .source(
                      """
                      // 필드 값이 특정 조건을 만족하는지 확인
                      def fieldValue = doc['field_a.keyword'].size() > 0 ? Integer.parseInt(doc['field_a.keyword'].value) : 0;
                      return fieldValue > params.threshold;
                      """
                  )
                  .params(Map.of("threshold", JsonData.of(100))) // 외부 파라미터 설정
        )
    ))
);

이 inline 스크립트는 field_a라는 필드가 100보다 큰 경우에만 문서를 반환하도록 설정되어 있습니다. inline 스크립트는 저장되지 않기 때문에 매번 새롭게 컴파일되며, 성능이 중요한 환경에서는 쿼리 방식이 더 유리합니다.


2. params

params는 외부 값을 스크립트에서 사용할 수 있도록 전달해 줍니다. 이렇게 하면 특정 값이 자주 바뀔 때 유용하게 사용할 수 있습니다. 다음 예제는 외부로부터 todayDate라는 값을 받아 필드의 값과 비교하는 경우입니다.

// params 옵션을 활용한 스크립트 예제
b.should(sh -> sh.script(
    sco -> sco.script(sco2 -> sco2
        .inline(
            i -> i.lang("painless")
                  .source(
                      """
                      def fieldValue = doc['field_a.keyword'].size() > 0 ? Integer.parseInt(doc['field_a.keyword'].value) : 0;
                      return fieldValue < Integer.parseInt(params.todayDate);
                      """
                  )
                  .params(Map.of("todayDate", JsonData.of("20231028"))) // 외부 날짜 파라미터 전달
        )
    ))
);

이 스크립트는 todayDate 값보다 작은 문서만 필터링하도록 설정되어 있으며, 파라미터 사용을 통해 여러 조건을 재사용할 수 있습니다.


3. lang

lang 옵션은 스크립트에서 사용하는 언어를 설정합니다. Elasticsearch에서는 주로 Painless를 사용하며, 성능과 보안이 최적화되어 있어 추천되는 언어입니다. 다른 언어를 설정할 수도 있지만, 보안과 성능상의 이유로 Painless가 권장됩니다.


4. id

ID를 사용하면 Elasticsearch에 스크립트를 등록하여 관리와 성능을 크게 개선할 수 있습니다. 등록된 스크립트는 재사용이 가능하고 매 실행 시마다 컴파일할 필요가 없어 성능이 향상됩니다. 아래는 스크립트를 등록하고 호출하는 예시입니다.

PUT _scripts/custom_script_id
{
  "script": {
    "lang": "painless",
    "source": "your script source here"
  }
}

위와 같이 Elasticsearch에 스크립트를 등록한 후, 다음과 같이 스크립트 ID를 호출하여 사용할 수 있습니다.

b.should(sh -> sh.script(
    sco -> sco.script(sco2 -> sco2.stored(
        st -> st.id("custom_script_id") // 스크립트 ID 사용
                .params(Map.of("today", JsonData.of(today))) // 파라미터 설정
    ))
));

이렇게 하면 스크립트 관리가 용이해지고, 성능 저하를 방지할 수 있습니다.


스크립트는 유연하고 강력한 도구이지만, 성능을 염두에 두어야 합니다. 성능이 중요한 경우에는 미리 필드 값을 전처리해 인덱싱을 하거나, 스크립트 없이 쿼리만으로 처리할 수 있는지 확인하는 것이 좋습니다. 스크립트를 활용하더라도 최소화된 로직으로 작성하고, 파라미터를 적극적으로 사용해 효율성을 높이는 것이 중요합니다.