User segmentation 시스템 개발기

Jongchan Park
Backpackr Team (idus, Tumblbug, Steadio)
10 min readFeb 15, 2023

안녕하세요? 본격적으로 글에 들어가기에 앞서, 저는 아이디어스에서 서버 개발을 담당하고 있는 박종찬이라고 합니다.

이 글에서는 디스플레이 영역에 유저가 관심 있을만한 콘텐츠를 보여주기 위해
유저 세그멘테이션 시스템을 개발했던 이야기를 해보려고 합니다.

이제 시작할게요!

새로운 요구사항의 등장: 유저 세그멘테이션

마케팅 팀에서는 전환율을 올리기 위해 다양한 시도를 하고 있었고,
그 시도 중 하나로 특정 기준을 정해 유저 세그먼트를 분류하여 타겟 유저 그룹에
적절한 콘텐츠의 배너를 보여주고자 하는 요구사항이 있었습니다.

세..세그멘트?

백엔드 개발자로서, 유저 세그멘테이션이 뭔지 모르는 것은 부끄러운 일이 아닐 것입니다. 혹시나 당시의 저처럼 유저 세그멘테이션이 뭔지 몰랐던 분들을 위해 잠시 설명을 하고 넘어갈게요.

유저 세그먼트란?

유저 세그멘테이션이란 커다란 그룹의 유저를 특정 기준에 따라 작은 집단으로 나누는 것을 의미한다. 유저 세그멘테이션의 목적은 유저의 행동과 선호, 필요로 하는 것을 이해하여 유저 경험을 증진 시키고 비즈니스적인 목표를 달성하는 것이다.

사용자가 분류되는 기준은 전부 말씀드리기는 어렵지만 최근 주문 일자,
최근 로그인 시각, 멤버십 가입 여부 등의 기준이 있었습니다.

사실 유저 세그먼트라는 것이 단순무식하게 구현한다면 수십 개의 where 절과 필요한 테이블 모두를 조인한 쿼리 하나로 모든 것을..해결할 수 있는.. 그런 것이죠.

하지만 아이디어스의 백엔드 서비스는 MSA 구조로 전환하는 과정에 있었고,
일부 서비스의 데이터베이스는 이미 분리가 되어 있는 상태였습니다.

전통적인 개발 방식과 단점

만약 전통적인 개발 방식으로 진행한다면,
유저의 마지막 주문일자를 기준으로 해당 유저가 어떤 세그먼트에 속하는지 판단 하려면, 주문 테이블에 쿼리를 날려 조회해야 했을 것입니다.

이 방식은 개발 자체는 심플해질지 몰라도 아래와 같은 단점을 갖게 됩니다.

  • 레거시 서비스를 담당하고 있는 메인 DB에 부하를 줄 수 있음
  • 즉 시스템 간에 장애 전파가 일어나기 쉬운 구조가 됩니다.
  • Microservice로 분리되어 서빙되고 있는 상태에서 서로 다른 디비를 조회해 응답하게 된다면.. 서비스하기에 적절한 latency를 보장하기 어렵습니다. 결과를 캐시를 하더라도 캐시를 타지 못한 최초 요청은 꽤나 오랜 시간이 걸리겠죠.

Materialized view(구체화된 뷰)의 도입

위에 언급된 단점을 해결할 수 있는 방법으로 떠오른 것은 구체화된 뷰를 구성하는 것이었습니다.

Materialized view는 MSA 구조에서 여러 서비스의 데이터를,
그것을 필요로 하는 서비스가 원하는 형태로 반 정규화해서 동기화된 복제본을 가지고 있는 것을 말합니다.

CQRS 패턴과도 밀접한 연관을 가지고 있는 패턴으로, read model 을 분리해서
저장하는 형태입니다. Chatgpt 에 물어보니 아래와 같이 잘 알려주네요.

What is materialized view in MSA?

생각중..

MSA 에서 머테리얼라이즈드 뷰는 미리 계산된 데이터 셋입니다. 머테리얼라이즈드 뷰는 각 마이크로 서비스가 가진 데이터를 복제하여 들고 있으면서, 필요한 구조로 비정규화된 데이터를 제공함으로써 조회 성능을 향상시킵니다.

마이크로 서비스 아키텍처에서, 일반적으로 각 마이크로서비스는 각자의 데이터를 저장하고 관리할 책임을 갖습니다. 이 때문에 여러 마이크로 서비스에서 복잡한 쿼리로 데이터를 조회해오기가 어렵습니다.

기존의 모놀리스 구조에서 데이터베이스의 join으로 처리하던 일을, MSA 구조에서는 application 로직을 통해 조인을 해야합니다. 이 방식은 조회성능 문제를 발생시키고, 쿼리에 조건을 걸기가 어려워지는 문제가 있습니다. 머테리얼라이즈드 뷰는 여러 마이크로서비스의 데이터를 하나의 데이터 셋으로 만듦으로써, 이 문제를 해결합니다.

머테리얼라이즈드 뷰는 조회가 많은 어플리케이션의 성능을 향상시킵니다. 미리 정의된 데이터 셋을 만들어 두기 때문에, 어플리케이션은 데이터를 훨씬 빠르게 찾아낼 수 있고, 전체적인 시스템의 부하를 줄일 수 있습니다.

머테리얼라이즈드 뷰는 보통 비동기로 업데이트가 되기 때문에, 완전한 실시간성을 보장하지는 못합니다. 이 단점은 전체적인 시스템 성능의 향상에 대비하면 대부분 납득할 만한 수준입니다.

결론적으로 머테리얼라이즈드 뷰는 마이크로서비스에서 데이터 조회를 최적화하는 유용한 기술입니다. 하지만, 데이터 동기화를 유지하기 위한 많은 노력이 필요합니다.

Materialized view는 다 좋지만, chatgpt 가 마지막 문장에 알려주었듯이 데이터 동기화 유지가 관건입니다. 개인적으로는 구체화된 뷰를 사용하면서 느꼈던 장단점은 아래와 같은 것이 있었습니다.

장점

  • 조회 성능이 우월하게 빠르다.
  • 시스템 간 장애 전파가 방지된다. 예를 들어 주문 시스템에 장애가 발생해도 segment 기능은 정상적으로 동작한다.

단점

  • 필드 추가시마다 전체 데이터를 다시 동기화해야 한다.
  • 데이터 동기화를 위한 추가 작업이 들어가며, 동기화가 깨질 위험성이 있다.
  • 비동기로 데이터가 동기화되기 때문에 완전한 실시간성을 보장하지 못함.

꽤 많은 단점에도 불구하고, 위 두 가지의 장점 때문에 구체화된 뷰 패턴을 사용하기로 결정했습니다.

데이터 동기화 방법

데이터 동기화는 MSA 구조상에서 Event driven으로 처리가 되기 때문에
Event driven architecture 와도 밀접한 연관이 있습니다.

예를 들면, 고객이 주문을 하는 경우 주문 시스템은 레거시 서비스에 존재하기 때문에, 레거시 서비스에서 kafka를 통해 결제 완료 이벤트를 발생시킵니다.

해당 이벤트에 관심을 갖는 각 마이크로서비스가 해당 메시지를 전달받아
필요한 로직을 수행하는데, marketing 서비스에서는 해당 이벤트를 전달받으면 Materialized view를 업데이트하도록 구성했습니다.

간략한 시스템 구조도

이를 위한 작업 순서는 아래와 같았습니다.

  • aggregator의 코드 내에 materialized view 동기화를 위한 이벤트 발행 로직을 작성합니다. 예를 들면, 로그인 시 로그인 이벤트를 발행하고, 결제가 완료되면 checkout 이벤트를 카프카로 발행합니다.
  • 이때 materialized view 가 초기화되고 유지되는 방식은 검색 색인 방법론의 정적색인, 동적색인과 동일합니다.
  • 먼저, 동적색인(증분색인)을 위한 코드를 개발해 배포합니다.
    materialized view 를 지속적으로 업데이트하기 위한 작업으로,
    aggregator에서 발행했던 이벤트 들을 marketing 서비스가 받을 때마다
    지속적으로 materialized view \를 업데이트합니다.
  • 이때 유의할 점은 이벤트 처리의 멱등성과 동시성을 잘 고려하는 것입니다.
    특히 업데이트 로직의 멱등성 유지는 정적 색인과 혹시 모를 장애 상황에
    이벤트 재처리를 위해 필수적입니다.
  • 이후 정적 색인을 위한 코드를 개발해 실행합니다. 이때 기존 데이터 소스에 존재하는 모든 데이터를 조회해 materialized view에 import 시킵니다.

잠깐.. 멱등성이 뭔지 모르시는 분들을 위해 잠깐 설명하고 넘어갈게요. 아신다면 넘어가셔도 됩니다.

멱등성?

멱등성을 유지하는 함수는 아래와 같은 수식을 갖습니다.

f(f(x)) = f(x)

이벤트 처리에서 멱등성이 유지되어야 한다는 말은, 같은 이벤트를 여러번 수신하더라도 결과적으로 저장되는 상태는 동일해야 한다는 뜻입니다.

위키백과에 따르면..

멱등법칙(冪等法則) 또는 멱등성(冪等性, 영어: idempotent)은 수학이나 전산학에서 연산의 한 성질을 나타내는 것으로, 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미한다. 멱등법칙의 개념은 추상대수학(특히, 사영작용소·폐포연산자 이론)과 함수형 프로그래밍(참조 투명성의 성질과 관련된)의 여러 부분에서 사용하고 있다. — 위키백과

MSA 구조에서 서비스 간 이벤트 처리에 있어서 대부분의 경우 멱등성을 유지하는 것을 권장합니다.

다시 돌아와서, 위의 과정을 마치고 나면 데이터 소스와 materialized view 간의 동기화가 준 실시간으로 유지됩니다. 간단하게 표현하면 아래와 같습니다.

실제론 훨씬 복잡하지만요

카프카 이벤트가 유실되지 않는 한 구체화된 뷰와 데이터 소스 간의 동기화는 유지됩니다. 만약 유실된 이벤트가 있다고 해도, 추가로 발행하면 되고, 혹시 이벤트를 수신해 처리하는 과정에 문제가 있었다면 kafka의 offset을 특정 시점으로 돌려 이벤트를 replay를 하면 됩니다. 동적색인 시 멱등성을 유지해야 하는 이유가 바로 이벤트 재처리를 위한 것이죠.

세그멘테이션의 구현

위에 설명한 과정을 통해 유저의 활동 데이터를 marketing 서비스의 데이터베이스에 저장했으니, 그다음은 해당 데이터를 사용해 유저가 어떤 세그멘트에 속하는지 판단하는 로직을 구현하는 것이었습니다.
유저 세그멘테이션 기능은 아래와 같이 단순한 질의에 대답만 하면 되는 간단한 일입니다.

“헤이, 1번 유저는 무슨 세그멘트에 속해?”

마케팅 서비스는 위 질의를 받으면, 먼저 캐시를 확인합니다. 캐시가 비어있으면 위에서 구현한 user 활동 관련 정보를 집약한 materialized view(user activity)를 조회하여 segment 분류 조건에 따라 해당 사용자의 세그먼트 리스트를 만들어 캐시에 저장합니다.

그리고 user activity에 대한 업데이트가 발생할 때마다 해당 유저에 대한 캐시를 제거하여 다음 요청에 갱신되도록 합니다. 플로우를 간단하게 sequence diagram을 그려보면 이렇습니다.

세그먼트 데이터 업데이트

세그먼트 데이터 조회

위 과정들을 통해 marketing service는 자체적으로 가진 뷰를 통해 필요한 유저 데이터를 유지하고, 해당 유저의 세그먼트를 매우 빠른 성능으로 계산해낼 수 있었습니다.

그래서..얼마나 빨랐냐고요?

우리 멍뭉이

Average latency는 3ms였으며, p99 latency 가 20ms 가 채 안 되는 빠른 속도의 조회 성능을 볼 수 있었습니다.

사실 어떤 서비스들은 이 정도의 응답속도까지 필요하지 않을 수는 있습니다.
여러분들도 트레이드오프를 잘 고려해 보시고, 성능과 응답속도, 장애에 대한 내성이 중요하다면 Event driven architecture와 materialized view를 도입해 보시길 권해드립니다.

그럼 긴 글 읽어주셔서 감사합니다.
잘못된 점에 대한 지적이나 토론은 언제나 환영이니 댓글 많이 남겨주세요!

Written by idus Core system Cell

Content writer
Jongchan Park

아이디어스팀과 함께 할 테크인재를 영입 중입니다.
지금 여기에서 지원해 보세요!

--

--