본문 바로가기
프로그래밍/Elasticsearch

[elastic search] 정규식으로 string replace 하기(_update_by_query)

by 동네로봇 2020. 12. 22.

elastic search에 '2020.01.01' 과 같이 날짜 string 형식으로 등록된 필드가 있었는데,

범위 검색과 정렬을 위해서 date 데이터 타입으로 변환해주고자 하였다.

 

esbook.kimjmin.net/07-settings-and-mappings/7.2-mappings/7.2.3-date

 

위의 사이트에 따르면

 

  • "2019-06-12"

  • "2019-06-12T17:13:40"

  • "2019-06-12T17:13:40+09:00"

  • "2019-06-12T17:13:40.428Z"

이와 같은 형식으로 데이터 저장 시 자동으로 date type으로 인식되어 저장된다고 한다.

 

그래서 현재 '2020.01.01'로 저장된 데이터를 '2020-01-01' 형식으로 update 하고자 하였다. 

 

하지만 결과적으로 replaceAll 에 성공하였지만 string을 바꾼다고 해서 저절로 data type이 변환되지는 않았다. 이 내용에 대해서는 다른 글에서 다루도록 하겠다.

 

문자열 변환을 위해서는 정규식을 사용해야 했는데,

엘라스틱 서치(이하 es) 에는 replace를 위한 특정 url 쿼리가 있는 것은 아니고 script를 통해서 필드값을 update 해주어야 했다.

 

update에 사용되는 script 언어는 Painless라고 하며 es 에서 사용되는 기본 스크립트 언어라고 한다.

자바 문법과 대부분 비슷하여 쉽게 사용할 수 있다. painless 에 대한 기본 내용 및 쿼리는 아래 doc 을 참고하였다. 

 

Painless Examples | Painless Scripting Language [6.8] | Elastic

 

Painless Examples | Painless Scripting Language [6.8] | Elastic

Starting in 7.0, doc['field'].value throws an exception if the field is missing in a document. To enable this behavior now, set a jvm.option -Des.scripting.exception_for_missing_value=true on a node. If you do not enable this behavior, a deprecation warnin

www.elastic.co

es 데이터 예시

{
"_source" : {
      "data": {
          "essential" : {
              "person" : {
                  "name" : {
                      "first" : "johnny",
                          "last" : "wang"
                  },
                  "birth" : {
                      "date" : "2020.01.01"
                  }
              }
          }
      }
   }
}

replaceAll 쿼리

POST /my_index/_update_by_query
{
	"script" : {
    	"lang" : "painless",
        "source" : "ctx._source.data.essential.person.birth['date'] = /\\./.matcher(ctx._source.data.essential.person.birth['date']).replaceAll('-');"
    },
    "query" : {
    	"bool" : {
        	"match" : {
            		"data.essential.person.name.first" : {
                		"query" : "johnny",
                   		"operator" : "and"
                	}
            }
        }
    }
}

 

설명하자면 "script"는 update를 위한 쿼리이고, "query" 는 특정 조건을 찾기 위한 조건 쿼리이다. (mysql where절과 같음)

전체 es 데이터를 업데이트 하려고 한다면 query 부분은 제외해도 된다.

 

근데 이 쿼리가 맞기는 하지만 처음 실행하면 다음과 같은 에러가 떨어진다. (끝에가 기억이 안남)

"type": "class_cast_exception", "reason": "java.lang.String cannot be cast to java.???"

 

그 이유는 doc에 나와있는데,

정규식을 잘못 사용하면 es 에 저장된 데이터에 손상이 갈 수 있기 때문에 default로 사용하지 못한다고 한다. 사용을 위해서는 elasticsearch.yml에 script.painless.regex.enabled : true 를 설정해 달라고 한다. ( 아유 귀찮아 )

 

elasticsearch.yml 파일은 서버의 /usr/share/elasticsearch/config/elasticsearch.yml 여기나 /etc/elasticsearch/elasticsearch.yml 경로에 있다고 합니다.

 

위에 파일을 수정한 후 systemctl restart elasticsearch 명령어를 통해 변경사항을 적용해 준다.

 

이후 update 쿼리를 다시 적용하면 '2020.01.01'이 '2020-01-01'로 업데이트가 된 것을 확인할 수 있다!!

 

하지만 앞에 적은 것처럼 데이터의 특수 문자만 바꾼다고 해서 date 타입으로 변환되지는 않았다. 해당 내용은 다음에 정리하는 걸로(reindexing)

 


주의

 

참고로 업데이트 쿼리를 다음과 같이 실행한다면, 엉뚱한 데이터가 insert 될 수 있다.

 

잘못된 쿼리

"source" : "ctx._source['data.essential.person.birth.date'] = /\\./.matcher(ctx._source['data.essential.person.birth.date']).replaceAll('-');"

쿼리 결과

{
"_source" : {
      "data.essential.person.birth.date" : "2020-01-01",
      "data": {
          "essential" : {
              "person" : {
                  "name" : {
                      "first" : "johnny",
                          "last" : "wang"
                  },
                  "birth" : {
                      "date" : "2020.01.01"
                  }
              }
          }
      }
   }
}

 

 

depth를 찾는게 아니라 이 결과처럼 그냥 string 필드가 만들어져 버릴 수가 있다. 

목적에 맞도록 쿼리를 잘 확인하여 update 해야한다.