Data Engineering

Elastic Scripting으로 승률 계산

728x90
반응형

들어가며..

원래는 엘라스틱 document를 공부하고 정리한 내용을 먼저 올리려 했으나...

진행 중인 프로젝트에서 나름 큰 진전(?)이 있어서 그 과정을 간단히 정리하게 됐다.

 

프로젝트에 관해서 간단히 말하자면 쿠킹덤 내의 아레나 콘텐츠의 데이터를 수집하고

그 데이터를 가지고 엘라스틱을 통해 여러 인사이트를 도출해보는 그런 프로젝트이다.

 

프로젝트에 대해서는 쓰고 싶은 이야기가 많지만

오늘은 간단하게  쿠키 조합별 승률을 기존 index에 존재하는

"WIN_CNT (승리 횟수)""ALL_CNT (전체 경기 수)" data를 가지고

scripting을 통해 승률로 변환 & 인덱스에 매핑하는 과정만 다뤄보겠다.

이론적인 부분은 제외하고.......


Scripting..?

어려운 개념은 아니고, 인덱스 내의 존재하는 데이터의 형태나, 수치, 기타 등등이

내가 원하는 형태가 아닐 경우, 알고리즘을 짜듯이 스크립트를 짜서 데이터를 변형해 사용하는 방법이다.

 

사용 예시를 들자면, 오늘 다루려고 하는 승리 횟수와 전체 경기 수를 가지고

각 조합별 승률 값을 계산해내는 방식의 사용이 가능하다.

 


과정

과정에 관해 말할건 딱히 없다...

scripting으로 동적 데이터 생성에 대해 공부를 하고 이 개념을 가지고 뭘 할 수 있을까 하다가

그동안 파이썬을 이용해서 했던 쿠키 조합별 승률 계산을 해보기로했다.

 

먼저 사용한 CSV 파일 형태를 살펴보면

쿠키 조합을 나타내는 COMBINATION_KEY

칼럼조합 내 쿠키를 나타내는 COMBIE_ONE ~ FIVE

칼럼승리 횟수를 나타내는 WIN_CNT

패배 횟수를 나타내는 LOOSE_CNT

전체 경기수를 나타내는 ALL_CNT

로 구성되어 있다.

 

위에 보이는 CSV 파일도 원본 데이터를 파이썬으로 가지고 놀다가 생성된 형태라

그냥 파이썬에서 승률을 계산해서 칼럼을 추가해주는게 더 편한 방법이긴 했으나..

aggregation를 공부하면서 "script를 통해 동적 데이터 생성이 가능하다"

너무 많이 보는 바람에 한번 써볼까 해서 시도해보게 되었다.

 

POST oven_combi_one_0721_/_search
{
  "size": 0,
  "runtime_mappings": {
    "cal_win_rate": {
      "type": "double",
      "script": {
        "source": "emit((doc['WIN_CNT'].value / doc['ALL_CNT'].value) * params.correction)",
        "params": {
          "correction": 100
        }
      }
    }
  },
  "aggs": {
    "ds": {
      "avg": {
        "field": "cal_win_rate"
      }
    }
  }
}

그래서 처음엔 위에 쿼리처럼 단순히 (승리 / 전체) * 100을 사용해

각 조합별 승률을 계산하고 aggregation을 통해 전체 승률에 대한 평균을 계산해보려했다.

 

"root_cause" : [
      {
        "type" : "script_exception",
        "reason" : "runtime error",
        "script_stack" : [
          "emit((doc['WIN_CNT'].value / doc['ALL_CNT'].value) * params.correction)",
          "                                           ^---- HERE"
        ],
        "script" : "emit((doc['WIN_CNT'].value / doc['ALL_CNT'].value) * params.correction)",
        "lang" : "painless",
        "position" : {
          "offset" : 43,
          "start" : 0,
          "end" : 71
        }
      }
    ]

그런데 계속 이런 에러가 발생했고, 처음엔 doc['ALL_CNT'].value 부분을 지목하길래

저 부분의 오류인가해서 값을 바꿔보기도 하고 별에 별 짓을 다 하다가 

오류 메시지나 다 읽어보자 하고 스크롤하며 읽던 중에 

 

"caused_by" : {
            "type" : "arithmetic_exception",
            "reason" : "/ by zero"
          }

메시지 끝 부분에 가서야 겨우 진짜 원인을 찾게 되었다.

 

사실 CSV 파일상에 문제는 이미 없는걸로 확인을 한 이후여서

도대체 전체 경기수의 값에 왜 0이 들어가는지 도통 알 수 없다가

match 쿼리를 써서 ALL_CNT 필드의 값이 0인 다큐먼트를 검색해보니

 

"_source" : {
          "ALL_CNT" : 0,
          "COMBI_FOUR" : 0,
          "COMBI_FIVE" : 0,
          "COMBINATION_KEY" : "COMBINATION_KEY",
          "COMBI_TWO" : 0,
          "LOOSE_CNT" : 0,
          "path" : "************************",
          "@version" : "1",
          "COMBI_THREE" : 0,
          "COMBI_ONE" : 0,
          "WIN_CNT" : 0,
          "@timestamp" : "2021-07-21T10:34:54.229Z"
        }

이런 형태로 잘못 들어간 다큐먼트가 하나 검색 되었다.

 

결국엔 CSV 파일에서 각 칼럼의 이름을 적어둔 row가 하나의 다큐먼트로 들어가버려서

"/ by 0" 에러가 발생했던거였다..

 

그래도 로그스태시 config 파일 필터에서 각 필드를 정적으로 매핑해준 덕분에

전체적인 데이터가 뒤틀리는 참사는 안 일어났지만 이전에도 같은 형태로 input했을 때는

생긴적 없던 오류여서 조금 당황스러웠다.

 

여기서 오류가 발생한 다큐먼트를 날릴 것인지 아니면 그냥 새로 input을 할 것인지 선택해야 했는데

기존 인덱스명에 오타도 있었고, 다른 팀원들도 사용하는 공용 클라우드 계정이라

깔끔하게 새로 input하기로 결정했다.

 

성공적으로 input하고 난 후에 이번에는 작성한 쿼리가 제대로 먹는지 보려고 

1번 조합에 대한 승률을 계산하고, 해당 값만 반환하는 쿼리를 실행해봤다.

 

GET oven_combi_one_0721_real/_search
{
  "query": {
    "match": {
      "COMBINATION_KEY.keyword": "combi1"
    }
  },
  "script_fields": {
    "calculate_win": {
      "script": {
        "lang": "painless",
        "source": "(doc['WIN_CNT'].value / doc['ALL_CNT'].value) * params.correction",
        "params": {
          "correction": 100
        }
      }
    }
  }
}

 

실행 결과로 대충 55에 가까운 값이 반환되야 하지만

또 무슨 문제인지 0만 반환되었다.

 

"fields" : {
          "calculate_win" : [
            0
          ]
        }

 

이번엔 또 뭐가 문제일까... 하고 승률이 100%가 나오는 조합을 찾아서 

해당 조합을 출력했더니 제대로 100이 반환되길래 더 혼란에 빠졌다.

 

그렇게 좀 멘붕(?)에 빠져 있다가 문득 처음에 날린 쿼리와 다르게

type을 double로 명시하지 않아서 오류가 생기나 하고

(double)doc['ALL_CNT'].value)

승리 값을 정적 매핑한 integer에서 double로 변환해 주었더니

정상적으로 결과가 출력됐다.

 

PUT oven_combi_one_0721_real/_mapping
{
  "runtime": {
    "CALCULATE_WIN_RATE": {
      "type": "double",
      "script": {
        "source": "emit((doc['WIN_CNT'].value / (double)doc['ALL_CNT'].value) * params.correction)",
        "params": {
          "correction": 100
        }
      }
    }
  }
}

그리고 위에서 작성한 스크립트를 기반으로 기존 인덱스에 매핑을 추가해주었고

대쉬보드를 통해 간단하게 승률 칼럼이 추가된 테이블을 생성할 수 있었다.

 

글이 굉장히 갑자기 끊기는 느낌이지만 일찍 자야해서....

거의 첫글(?)은 이정도로 마무리해야될 것 같다..

글 수정은 아마... 할 수도 있고,,, 아닐 수도 있고,,,

728x90
반응형