Analyze Data in MongoDB with AWS

Sunghoon Kang
Banksalad Tech
Published in
9 min readSep 10, 2018

Con-Salad 01 🌽🥗에서 발표된 내용입니다.

뱅크샐러드에는 사용자의 데이터를 분석해 조금 더 맞춤화된 자산관리를 돕는 금융비서라는 기능이 있습니다. 예를 들면 소비가 급격히 늘었을 때 과소비 경보 조언을 보내주고, 지난밤 과하게 술을 마셨을 때는 음주 경고 조언을, 반대로 평소보다 술을 덜 마셨을 때는 절주 칭찬 조언을 발송해주는 등 사용자에게 개인화된 자산 관리 경험을 제공하고 있습니다.

그러던 어느 날, 금융비서 PM 님이 회의시간에 의견을 주셨습니다.

(사용자의 데이터를 기반으로) 특정 사용자 집단을 대상으로 다양한 실험을 해보고 싶어요!

I. WILL. BE. BACK~~~~~

예를 들면 현대카드를 보유하고 있는 사용자에게 슈퍼콘서트 공연에 관한 조언 발송 후 조언 클릭률과 좋아요 클릭률 등을 측정해 어떤 형태의 조언을 시스템화할지 의사결정을 한다면, 금융비서 프로젝트가 전반적으로 좀 더 기민하고 효율적으로 돌아갈 수 있겠죠.

하지만 이렇게 특정 사용자 그룹을 타겟팅할 수 있는 시스템과 인프라를 개발하기에는 너무 많은 시간이 소요된다고 판단하여 단순히 간단한 쿼리만으로 이 문제를 해결할 수 없을지 생각해보게 되었습니다.

뱅크샐러드의 데이터 대부분은 MongoDB에 저장되고 있습니다. (당연히) 환경별로 DB는 분리되어 있지만, 별도의 분석용 DB가 따로 없었습니다.

1TB가 넘어가고 10억 개 이상의 Document가 쌓여있는 DB에 복잡한 쿼리를 날리면 어떻게 될까요?

Index 없이 쿼리를 날리는 경우 모든 DocumentScan 하는 탓에 Disk IOPS가 치솟아 서비스 전반에 영향을 끼칠 수도 있고,(물론 권한 설정을 제대로 하면 그럴 일은 없겠지만) 잘못된 쿼리를 날리는 경우 데이터가 날아갈 수도 있겠죠.

그러면 Hidden Member를 사용하면 되지 않나요?

현재 뱅크샐러드에서는 Managed ServiceMongoDB Atlas를 사용하고 있는데, Atlas ClusterHidden Member를 지원하지 않으며 특정 쿼리를 위한 Index를 계속 관리해줘야 한다는 점 때문에 MongoDB는 분석용으로 적합하지 않다고 생각했습니다.

원하는 데이터를 어떻게 안전하고, 빠르고, 편리하게 뽑을 수 있을까?

조사 중 SQL을 사용해 Amazon S3에 저장된 데이터를 간편하게 분석할 수 있는 서비스인 Amazon Athena를 발견하게 됩니다. 그러면 MongoDB에 있는 Data를 어떻게 Athena로 분석할 수 있을까요?

먼저 사용자가 금융 정보를 갱신할 때마다 생성되는 Snapshot을, 1) 자정에 Kubernetes Cluster에서 작동하는 CronJob을 통해 JSON으로 변한한 뒤 2) S3에 저장한 후, 3) Athena로 데이터를 분석하는 Pipeline을 개발하면 어떨지 개발팀과 토의 했고, 이 구조로 개발하게 되었습니다.

이렇게 올라간 JSON FileAthena에서 쿼리를 날려보려고 했더니, Table 정의가 필요하다는 사실을 깨닫게 되었습니다. 그런데…

기준일
계좌 | 종류 / 금융사 / 잔액 (금액 / 통화) / 개설일 ...
카드 | 이름 / 카드사 ...
...

기존 자산 데이터의 Schema가 변경되거나 특정 자산이 새롭게 추가되는 경우 매번 Table Schema를 수정하는 건 굉장한 비효율이라고 느꼈습니다.

괜찮아요? 많이 놀랬죠

그러다 관련 문서를 보던 중 분석을 위해 손쉽게 데이터를 준비하고 로드할 수 있도록 지원하는 완전관리형 ETL 서비스인 Glue를 만나게 됩니다.

Glue에서 제공하는 Crawler를 통해 클릭 몇 번으로 위와 같이 DataSchema를 뽑아낼 수 있습니다. 만약 특정 구조체의 Schema가 이상하게 인식되더라도 아래와 같이 직접 수정할 수 있으니 걱정하지 마세요!

이제 쿼리를 날리기 위한 모든 준비를 마쳤으니 Athena를 활용해 뱅크샐러드에서 카드를 등록한 사용자 집단을 찾는 쿼리를 날려보도록 하겠습니다.

FYI — 쿼리 결과는 Athena 대시보드 상단의 History 탭에서 확인하실 수 있으니 시간이 너무 오래 걸린다면 중간에 창을 닫으셔도 됩니다.

원하는 결과를 얻어 좋지만, 너무 느립니다. 어디서부터 잘못된 걸까요?

위 쿼리 결과에서 이상한 점을 서술하세요 (5점)

정답 — JSONFull Scan하고 있어요

WITH latest_summaries AS
(SELECT user_id AS uid,
max(base_datetime) AS mdt
FROM user_asset_summaries
GROUP BY (user_id))
SELECT user_id
FROM user_asset_summaries s
JOIN latest_summaries ls
ON s.user_id = ls.uid
AND s.base_datetime = ls.mdt
WHERE cardinality(s.cards) > 0

쿼리를 위해 필요한 Columnuser_id, base_datetime, cards 이 세 Column인데, 모든 Row를 다 스캔하고 있어 매우 느리고, 굉장히 비효율적입니다. JSONColumn 형식으로 변환한다면, Athena에서 쿼리에 사용되는 Column만 스캔하기 때문에 쿼리 실행 속도가 비약적으로 빨라지게 됩니다.

그러면 JSON File을 어떻게 Column 형식으로 변환할 수 있을까요? Table을 생성할 때처럼 Glue를 사용하면 됩니다! GlueApache Spark 기반의 서비스이기 때문에 Spark 코드를 작성하고 Glue Job으로 등록하면, 특정 이벤트가 발생했을 때 (예를 들면 S3에 파일이 업로드되었을 때, 혹은 Glue Crawler가 동작했을 때 등) 자동으로 실행되도록 설정할 수 있습니다.

그러면 JSONColumn 형식 중 하나인 Parquet로 변환하는 코드를 살펴보겠습니다.

먼저 SparkData Frame과 유사한 개념인 GlueDynamic Frame을 기존 JSON DataCatalog에서 가져옵니다. Dynamic FrameSchema 없이 각 Recordself-describe 한다는 점을 제외하고는 Data Frame과 같습니다.

가져온 Dynamic FrameData Frame으로 변환한 뒤, 특정 Column에 대한 Transform을 처리할 수 있습니다. 위 코드는 string으로 저장된 base_datetime 이라는 Columntimestamp로 변환하는 코드입니다.

마지막으로 Data FrameS3로 내보내기만 하면 끝! 🎉 참 쉽죠? 위 코드에서는 Parquet에서 권장하는 Snappy로 압축하는 내용까지 포함되어 있습니다.

그래서 얼마나 효율적으로 쿼리를 날릴 수 있게 되었나요?

시간은 거의 94% 정도 단축되고, 쿼리를 위해 스캔한 데이터의 크기도 1/3 정도로 줄어들었습니다!

그런데 작업 로그를 살펴보니 실행 시간이 선형적으로 증가하는 걸 발견할 수 있었습니다. 그 이유는 이미 Parquet로 변환된 JSON 파일도 다시 Parquet로 변환하기 때문인데요. 작업 북마크를 활성화하면 이 문제가 해결됩니다.

속성에 숨어있는 작업 북마크…

🍯 Tip — File Size Matters

HBO의 Silicon Valley 꼭 보세요

JSONParquet로 변환하는 게 까다롭다면, 파일을 압축하는 걸 추천합니다. Athena의 경우 스캔한 데이터의 크기를 기준으로 비용이 계산되는데, Athena는 압축된 파일도 지원하므로 압축을 하는 게 비용 절감에 도움이 될 수 있습니다. (지원되는 압축 포맷은 여기서 확인하실 수 있습니다)

글을 맺으며

개발 기간을 최소화하며 빠르게 사용자에게 조언을 발송해 다양한 실험을 해볼 수 있다는 점에서 인상 깊었던 프로젝트였습니다. 실제로 카드사 프로모션 조언, 공항 라운지 프로모션 조언 등 다양한 조언을 발송해 여러 의미 있는 데이터를 뽑아낼 수 있었다는 점에서 재밌던 경험이었습니다.

하지만 시간관계상 진행하지 못한 부분이 있다는 게 아쉬웠습니다. User Snapshot의 경우 time series data이기 때문에 data partitioning을 통해 좀 더 최적화 할 수 있었을 것 같고, 비록 이번에는 Parquet만 사용해보았지만, 다음에는 ORC도 사용해보고 두 포맷에 대한 비교도 (언젠가는) 진행해보고 싶습니다.

끝으로 레이니스트에서는 모든 직군의 엔지니어를 채용 중입니다. 함께 기술적인 문제들과 사용자에 대해 고민하며 더 나은 제품을 만들고 싶으신 분은 아래의 배너를 통해 언제든지 지원해주세요 💗 😎

--

--

Sunghoon Kang
Banksalad Tech

Software engineer who is interested in developer productivity and happiness