요약
- 새로운 회사에서 못해도 주에 최소 4시간 이상은 인사이트 담당자분의 시간을 투입해야 하는 데이터 분석 건이 있었다.
- 여기에 시간이 많이 드는 반면, 간소화 하지 못하는 데에는 2가지의 이유가 있었다. 하나는 필요한 데이터가 들어있는 DB가 2개 이상으로 분리되어 있어서 둘 사이에 join을 걸 수 없다는 것과, 다른 한가지는 유저 ID 기준으로 어떤 Action을 했는지 각 Action 별 Count를 세어야 하는데 그 대상이 되는 테이블이 30Gb을 넘다 보니, Groupby + Count Query로 접근하면 퍼포먼스 이슈가 있다는 점이었다.
- 파이썬으로 각 DB에서 필요한 데이터를 불러온 뒤, Pandas로 분석하여 Report를 만든 후, 회사의 Slack 채널에 뿌려주는 Script를 만들었다. 이를 통해 이 업무는 하루 한번 버튼을 눌러주면 되는 수준의 업무로 완전히 개선되었고, 주 1회 발행되던 리포트는 매일 최근 8일치 데이터를 보여주는 형태로 바뀌었다.
- 당연히 우리 팀은 오롯이 '분석'에만 초점을 맞출 수 있게 되었고, 분석된 데이터를 바탕으로 보상 정책을 수정할 수 있게 되었다는 행복한 엔딩.
- 그리고 그 과정에서 나는 약간의 개발 지식과, 파이썬을 활용한 환경 설정, 그리고 환경변수를 활용하여 중요 정보를 GitHub에 올리지 않는 방법을 배웠다.
왜 하게 되었나?
사설
파이썬으로 업무를 자동화 하는 것에 본격적인 관심을 가지기 시작했던 시점은 사실 2018년이었는데, 당시에 회사에서 ML 기반의 프로젝트를 담당하게 되면서 어께너머로 본 것도 있고, 이때를 기점으로 따로 이런저런 수업을 듣기도 했었다. 사실 이것 외에도 주식 관련 통계라던가, 경매 알리미 같은 토이 프로젝트를 하며 공부를 이어가고 있었다.
이전 직장인 라이엇 게임즈에서 아쉬웠던 점 중 하나가, 생각보다 회사 업무를 하면서 이런 부분을 직접 다룰 만한 기회가 생기지 않는 다는 것이었는데, 데이터 자체는 엄청나게 많지만 내 업무의 연관성이 아무래도 이쪽에 직결되지 않다 보니, 뭔가 의미있는 프로젝트를 스스로 만들기가 좀 어려웠다. 그러다 겨우겨우 발견했던 아이템 중 하나가 결국 마케팅팀과 인플루언서를 담당하는 팀이 유튜브 조회수를 한땀한땀 손으로 조사한다는 것이었고, 그렇게 그렇게 나온 토이 프로젝트 중 하나가 이 블로그에도 올려둔 유튜브 조회수 수집기이다.
진짜 배경 및 문제 정의
22년에 새롭게 합류한 플라네타리움에서는, 내가 담당하는 Product가 아무래도 유저의 Retention과 이를 위한 보상에 직결되는 서비스이다 보니, 자연스럽게 통계 데이터를 볼 일이 많아졌다. 함께 일을 하는 사업 PM 분이 워낙에 SQL을 잘 다루셔서, 대부분의 데이터는 가공된 형태로 받아볼 수 있었는데, 문제는 가끔 DB에 없는 데이터와 join을 하고자 하는 니즈가 발생한다는 것이었다. 그래서, 소스가 다른 두 곳의 데이터를 가져와야 한다면, 파이썬 스크립트로 이를 해결할 수 있지 않을까? 하는 생각에 착수하게 되었다. 덤으로, DB Query도 수동에서 자동으로 변경할 수 있을테니,일석 이조의 효과를 기대할 수 있었다.
게다가, 스타트업이라 회사에 리소스가 워낙 귀하다 보니, 다른 개발자 분들이 더 중요한 일을 할 수 있도록 방해를 하고 싶지 않은 마음도 있었고, 또 개인적으로 사이드 프로젝트로 회사 업무 효율을 높이는 경험을 해보면 좋겠다 싶어, 좋은 기회라 생각했다. 회사에도 득이 되고, 동료에게도 득이 되고, 나에게도 득이 되는, 안할 이유가 없는 프로젝트였다. 유일한 문제는 시간이라, 다른 일이 너무 안되거나 퇴근 후에 짬짬이, 토이 프로젝트로 접근하며 한발씩 천천히 나아갔다.
문제 1: 여러 곳에 분산되어 있는 데이터
우리 서비스의 분석을 위해 필요한 데이터는 크게 두 곳에 담겨 있다. 우선 서비스 자체가 둘로 나뉘어져 있었다. 블록체인 위에 모든 데이터가 올라가 있는 우리 회사의 게임과, 그 게임을 플레이하는 유저들의 온보딩 및 리텐션을 담당하는 '포탈'의 DB였다. 다행히 블록체인 쪽 데이터는 분석을 위해 RDB로 내려주는 서비스를 내부 개발자 분이 미리 만들어 주셔서 2가지 데이터 모두 RDB를 대상으로 Query를 하면 되는 상황이엇지만, 데이터가 두 곳으로 나뉘어져 있으니, 이 둘을 join하는 것은 임시 table을 만들어 update를 하지 않는 이상 쉽진 않았다. 심지어 한쪽은 블록체인 데이터의 사본인 '읽기' 전용의 DB지만, 다른 한쪽은 라이브 서비스를 진행 중인 Production DB였다. 이 DB의 사본을 만들어 달라고 하는 것도 물론 가능했지만, 위에도 적었듯이 '그 작업을 할 시간에 다른 더 귀한 일'을 해야 하는 스타트업이다. DB를 이중화 하기 보다는, 가벼운 Query를 던져 서비스에 무리가 가지 않는 수준에서 같은 DB를 쓰는게 우선은 더 낫다고 판단했다.
문제 2: 연산이 무거워서 DB 에 직접 Query를 하기 어려움
우리는 게임 데이터를 바탕으로, 게임을 열심히 하는 열성 유저 (Heavy, 이하 H그룹) 그룹과 일일 보상 정도만 챙기는 라이트 유저 (이하 L그룹) 을 나누고자 했는데, 문제는 이 연산 자체가 매우 방대했다. 우리 게임의 특징은, 모든 Action이 다 블록체인에 기록된다는 점인데, 이렇다 보니 하루에도 수십만 줄의 Action 데이터가 남았다. 분석하기엔 좋았지만, DB에 대놓고 직접 query를 던지기엔 퍼포먼스 이슈가 컸다. 특히 그 Action 관련 Table은 우리 블록체인의 '태초(Genesis)' 블록부터 데이터가 있어서, 그 용량이 30Gb을 가볍게 웃돌았다. 여기에 대놓고 Unique, Groupby, COUNT 같은 걸 날리기엔, 아무리 최적화를 해도 살떨리는 일이었다.
자, 문제를 파악했고. Solution도 생각보다 간단했다.
무었을 했나?
문제1, 2가 워낙 직관적이고 심플해서, 구현만 하면 되었다. 그래서 간단하게 설계를 해보았다. 설계를 다 하고 나니, 구현이 그리 어려울 것도 없어 보였다. 아마 평소에 SQL이나 Pandas 에 익숙한 분이 했다면 넉넉잡아 2-3일 만에 마칠 수 있는 업무의 분량이 아니었을까 싶다.
- 우선 데이터가 분산되어 있으니 하나의 공간으로 모아야 했다. 어차피 데이터를 받아온 뒤의 처리는 Pandas를 이용할 예정이었으므로, 우선 local로 다 모으는 쪽으로 했다.
- 유저를 H그룹과 L그룹으로 나누기 위해서, 가능한 긴 기간이면서도 DB에 부하가 어느정도 감내 가능한 수준의 구간을 잡으려고 했다. 이 과정에서 일주일 정도가 적당하다고 결론을 내렸고, Action 관련 테이블에서 7일치 데이터만을 가져오기로 하였다. 그렇게 한번씩 가져오면, 약 0.4Gb 정도가 local에 쌓였다. (이 부분은 결국 추후 개선이 필요하게 된다. 한방에 0.4Gb를 긁어오는 Query라니...)
- H그룹과 L그룹으로 나누는 코드 자체도, 결국 Pandas로 했다. 각 유저의 고유 ID (Web3 게임이니 Address를 기준으로) 를 기준으로, 일자별로 - 어떤 유저가 - 어떤 Action을 몇번씩 했는지, Count만 남기는 식으로 Aggregate한 테이블을 임시로 만들었다. 그 테이블을 바탕으로, 내부 정책에 따라 평균 Score를 메긴 뒤, H그룹과 L그룹으로 구분했다. 결국 각 그룹에 속한 유저의 고유 ID를 알 수있게 되었다.
- 결국 하고자 했던 것은, H그룹은 더 보상을 많이 가져가고, L그룹에 지출되는 보상의 총량은 합리적인 수준까지 낮추는 것이었다. 보상을 제공할 수 있는 Pool이 한정적인 만큼, 더 충성도가 높고 활동이 많은 고객이 보상을 더 가져가게 하는 것이 서비스를 위해 맞는 방향이었다. 그래서 최근 7일 동안 H그룹과 L그룹이 가져간 보상의 총량을, '보상' 테이블과 위에서 만든 그룹별 ID를 매핑하여 산출할 수 있었다.
위 까지가 분석에 해당했고, 처음에는 이를 csv 파일로 떨구게 만들었다. 이를 사업 PM 분의 PC에도 설치해드리고, 내 PC에도 설치를 했는데 뭔가 좀 아쉬웠다. 이전에 텔레그램 경매 알리미를 만들었던 경험을 살려서, 많은 사람들이 편하게 볼 수 있도록 Slack에 쏴줘야지- 라는 곳까지 생각이 나아갔다. 기존에도 tabulate를 이용하면 테이블을 이쁘게 마크다운으로 보여줄 수 있다는 것을 알고 있어서, Slack 채널에 본문으로 보여주고 CSV 파일도 함께 업로드 해주는 기능까지 구현을 해버렸다.
이 과정에서의 챌린지 + 헤딩
우선 DB 종류가 다르고, DB 관련 Python Library를 써본 적이 없었다.
DB 중 하나는 MySQL인데, 다른 하나는 postgresql 이었다. Query도 아주 약간 다르긴 했는데 무시해도 될 수준이었지만, 문제는 내가 Python으로 DB에서 직접 데이터를 가져오는 것을 해본 적이 없다는 것이었다. MySQL의 경우는 Pandas 자체에서 pd.read_sql 이라는 기능을 제공했지만, psql은 경험이 없어서 psql을 지원하는 library를 찾아보아야 했다. 이 과정에서, psycopg2가 이를 담당해준다는 것을 알게 되었고, 맥용 라이브러리는 어째서인가 살짝 다른 버전을 install 해야 한다는 것 외에는 크게 문제가 없었다.
Jupyter notebook, data type, convention...
사실 DB Connection 자체를 처음 해보기도 하고, Pycharm 등의 IDE에서 디버깅을 하는 방법을 아직 잘 모른다. 그래서 Anaconda와 Jupyter notebook을 활용해서 코드를 한줄한줄 돌려보며 작업을 진행했고, 코드가 얼추 검증되고 나면 이를 pycharm으로 옮겨서 함수형으로 코드를 정리했다. 이 과정에서 가능하면 가까운 미래에 내 산출물을 보고 내부 엔지니어 중 누군가가 이를 서버에 올려주거나 좀 개선해주지 않을까 싶은 마음에, 최대한 주석을 잘 달고 변수명도 좀 Convention을 맞추어 해보고 싶었다. 근데 역시 숙련도가 부족하여 잘 안되긴 했다. Convention도 엉망이고. 대략 아래와 같달까.
늘 이런 일을 할 때 내가 제일 어려운 건 Data type에 따른 처리이다. 위의 주석에도 달아두었지만, 아직까지 나는 Data type에 따른 처리가 경험이 부족하다. 매번 봇 만들때마다 이전에 했던 노하우도 싹 기억에서 초기화 되는 것 같고. 그래도 일단 잘 돌아가니 다행!
결과?
그래서 그 산출물은 아래와 같이 생겼다. 아직은 하루에 한번, 내 로컬 환경에서 '실행'을 눌러주어야 동작하는 말 그대로 스크립트 이고, 내 맥이 잠자고 있는 동안에는 실행을 할 수 없어서 아직 Scheduler를 걸어두진 못했다. Slack bot으로 만들어 두었기 때문에, 내가 지정한 채널에 하루에 한번 리포트를 올려준다. 예전엔 주간 미팅에서 한번씩 지표를 보여주는 것이 전부였지만, 이제는 관심이 있는 사람은 누구든 채널에 와서 이 지표를 볼 수 있다. 심지어 슬랙에 최적화된 형태로 뿌려주는데 고민을 많이 해서, 아래 보는 것처럼 예쁜 표로 (디자이너 눈엔 어디가 이쁘냐 하겠지만) 출력이 되며 CSV 파일도 준다.
기존에는 한번 데이터 보려면 못해도 한 3-4시간은 금방 날아가는 작업이었는데, 이를 버튼 한번만 누르고 한 10분 기다리면 되는 수준으로 (물론 10분동안 딴짓하면 알아서 채널에 올라가 있다) 획기적으로 단축을 해냈다.
위에 요약에도 적었지만, 당연히 우리 팀은 이제 '분석'에만 초점을 맞출 수 있게 되었고, 분석된 데이터를 바탕으로 보상 정책을 수정할 수 있게 되었다는 행복한 엔딩이다. 실제로 작년 말 기준, 보상 관련 최적화로 지표가 많이 개선되기도 하였고, 현재 기준으로는 내가 없는 날엔 리포트가 안나가니 회사 내에서 존재감을 어필하기에 나쁘지 않은 접근인 것 같기도 하다.
그리고 그 과정에서, 나는 개발적으로 크게 2가지를 더 배웠는데, 하나는 requirements.txt로 python library들을 빼서 다른 PC나 환경에 쉽게 프로젝트를 세팅할 수 있도록 하는 것과..
지금까지 이론으로만 알고 있던, 중요한 credential 같은 정보를 .env로 빼는 방법을 우리 회사 다른 시니어 엔지니어분에게 배울 수 있었다! 이론만 알고 있어서 어떻게 하는지가 매우 궁금했는데, 이제 어떻게 하는지 배웠으니 이후에 개인 프로젝트에서도 유용히 써먹을 수 있겠다.
남은 과제
- 원래 내부 엔지니어 분의 도움을 받아, 위 스크립트를 하루에 한번 동작하도록 t1 micro 같은 인스턴스에 올려두려고 했었는데...
- 최초에 Script 자체를 1주일에 한번 weekly report를 만든다고 가정하고 작성을 하다 보니, 데이터가 7일치가 필요하여 0.3~0.4gb 짜리 데이터를 DB connection을 열고 받아온다. 결국 내가 만든 스크립트가 인스턴스를 죽였고 ㅋㅋㅋ 최적화를 해야 올릴 수 있을 것 같다고 피드백을 받았다.
- 이왕 스케쥴러를 설정할 수 있는 환경에 두기로 하였으니, 로컬 캐시를 두고 저 7일치 데이터를 최대한 작게 슬라이싱해서 가져와서, 그 결과값만 남기는 식으로 코드를 개선할 예정이다.
스케쥴러로 동작한다고 가정을 하면, 지금 통으로 짜둔 함수들을 좀 기능별로 나누어서 refactoring을 하고, 데이터 수집과 데이터 후가공, 리포트 가공 등으로 코드를 나누긴 해야 할 것 같다. 이 부분은 짬이 되면 더 하거나, 팀에 새로 오시는 엔지니어 분에게 토이 프로젝트로 넘겨드리려고 한다.
이상, 이직 후 개인적으로 가장 마음에 드는 성과 중 하나에 대한 회고를 마쳐본다.