Programmable Realtime Webhook System

Programmable Realtime Webhook System

Postback 이야기

모바일 마케팅에서 Postback은 매우 중요합니다. 광고 매체에서는 앱 설치 또는 앱 내 전환을 유도하기 위한 광고들을 지면에 띄우고, 광고를 클릭하는 것까지는 추적할 수 있습니다. 하지만, 앱 스토어나 앱 내로 이동한 뒤의 데이터는 앱에 광고 매체의 SDK가 설치되어 있지 않은 이상 추적이 불가능합니다.

Postback이 중요한 이유

집행하는 광고 매체가 수십 개까지 될 수 있기 때문에 앱에 모든 광고 매체의 SDK를 설치할 수는 없습니다. SDK 하나하나를 설치하는 것에 개발자 리소스가 필요하기 때문입니다. SDK가 많아질수록 앱이 무거워지는 것도 문제입니다. 그래서 보통은 Airbridge와 같은 MMP 솔루션의 SDK 하나만 설치하고, MMP에서 광고 매체로 다시 데이터를 보내주는 식으로 연동합니다. 업계에서는 다시 데이터를 보내주는 것을 “Postback”이라 부르고 있습니다.
매체에서 Postback을 받으면 그 데이터를 어떻게 활용할까요? Postback을 이야기할 때 항상 따라오는 키워드는 “최적화” 입니다. 광고 매체 입장에서는 더 많은 성과를 내기 위해 전환이 더 잘 발생하는 사람들에게 더 많이 광고를 보여주고 싶을 것입니다. 또는, 앱 설치를 유도하는 캠페인일 경우 이미 설치한 사람들에게는 광고를 보여주지 않게 할 뿐만 아니라 이미 앱은 설치되어 있으니 앱 내 전환을 유도하는 광고를 노출하고 싶을 수 있을 겁니다.
Postback을 받으면 이러한 캠페인 최적화를 실시간으로 할 수 있습니다. 그렇기 때문에 매우 많은 매체가 Postback 연동을 하는 것을 강력히 권장하고 있습니다. 구글, 메타, 틱톡과 같은 한 번쯤은 이름을 들어봤을 곳들뿐만 아니라 몰로코, 애피어 등도 마찬가지입니다. Postback은 캠페인 최적화에 매우 중요한 역할을 하므로 광고 매체들도 민감하게 대합니다. 과거 Airbridge에서 시스템 유지보수 때문에 잠깐 postback이 중단된 때가 있었는데 곧바로 매체로부터 Airbridge로부터 postback 수신이 중단됐는데 무슨 일이 있는 건지 묻는 연락을 받기도 했습니다.

Airbridge에서의 Postback

Airbridge에서는 postback 시스템을 광고 매체와의 연동뿐만 아니라 실시간 데이터 연동이 필요한 다른 곳에서도 활용하고 있습니다. 예를 들면, Amplitude 연동 기능입니다. Amplitude도 자체 SDK가 있긴 하지만, Airbridge SDK가 설치되어 있다면 Amplitude의 server to server API를 실시간으로 호출하여 데이터를 보내줄 수 있습니다. 특히, Amplitude 자체 SDK만 설치한 경우에는 광고에 의한 성과를 구분할 수 없지만, Airbridge에서 분석 결과를 보내주면 Amplitude에서 유입 경로를 분석할 수 있기 때문에 유용합니다. 그 밖에도 실시간 데이터 연동이 필요한 많은 곳에서 postback 시스템을 활용할 수 있습니다.
Postback이라는 단어 자체는 생소할 수 있지만 프로그래밍 세계에서는 이미 널리 쓰이고 있는 기술 용어가 있습니다. 바로 Webhook 입니다. 이번 글에서는 Airbridge에서 postback 기능을 지원하기 위한 programmable webhook 시스템을 개발한 이야기를 다뤄보겠습니다.

해결해야할 문제

아쉽게도 Postback을 위한 표준이 존재하진 않습니다. 그렇기 때문에 광고 매체마다 다양한 방식으로 Postback 수신을 위한 API들을 가지고 있고, spec도 제각각입니다. Airbridge에서는 이미 100여 곳 이상의 매체와 연동이 되어 있지만, 앞으로 수천 곳 이상의 매체와의 연동을 계속 해야 합니다. 한국뿐만 아니라 전 세계 곳곳에 광고 매체들이 자리 잡고 있기 때문입니다.
해결해야 할 문제를 정리하면 다음과 같았습니다.
1.
새로운 매체와의 연동을 쉽게 할 수 있어야 합니다.
매체에서는 제각각의 API spec을 가지고 있고, 그 spec에 맞춰서 데이터를 변환하여 보내야 정상적으로 연동이 됩니다. 이러한 연동 작업은 심지어 매일, 하루에도 여러 번 이뤄질 수도 있기 때문에 매번 개발자가 직접 작업할 필요 없어야 합니다.
그나마 Postback 수신을 위한 API를 다들 HTTP로 제공하고 있다는 점이 다행이었습니다.
2.
대시보드를 통해 광고주마다 Postback 연동을 쉽게 켜고 끌 수 있어야 합니다.
개발된 시스템을 사용자가 대시보드에서 자유롭게 다룰 수 있어야 합니다.
새로운 매체와의 연동을 쉽게 할 수 있는 시스템을 만드는 것을 하고 나면 대시보드에서 그 시스템을 다룰 수 있도록 API 제공을 하는 것을 자연스럽게 이어서 할 수 있습니다.

연동 시스템에서 제공해야할 기능

연동 작업에 실제로 필요한 기능은 무엇일까요? 아래 이미지는 Airbridge의 postback 연동 메뉴를 캡처한 것인데요. 이를 참고하여 살펴보겠습니다.
1.
수집된 이벤트 중 필요한 이벤트만 전송할 수 있어야 합니다.
광고주 서비스로부터 수집되는 이벤트는 굉장히 다양하지만, 매체에게 보내야 할 이벤트는 일부입니다. 광고주 입장에서는 서비스에서 발생하는 이벤트 데이터가 소중하고 민감한 데이터이므로 함부로 공유하고 싶지 않기 때문입니다.
예를 들면, 애피어를 통해 앱 설치 캠페인만 집행하는 경우 앱 설치에 대한 postback만 전송하고 앱 내 구매 이벤트는 postback을 보내지 않는 식으로 연동할 수 있어야 합니다. 기여 정보가 조건에 사용되는 경우도 있습니다. 애피어가 설치하게 만든 유저들의 이벤트 데이터만 애피어에게 보내는 걸 허용하고, 몰로코가 설치하게 만든 유저들의 이벤트 데이터는 애피어에게 보내지 않게 하는 식입니다. 그 밖에도 다양한 조건들이 존재합니다.
2.
이벤트 데이터를 특정 HTTP payload로 변환할 수 있어야 합니다.
앞서 여러 번 언급했듯이 매체마다 각각의 API spec을 가지고 있습니다. 어떤 매체는 API key를 HTTP header에 명시하게 하지만 어떤 매체는 query parameter에 명시하도록 합니다. 어떤 매체는 항상 GET method를 사용하는 반면 어떤 매체는 POST method로 데이터를 받습니다.
함수형 프로그래밍에 익숙한 분이라면 곧바로 filter와 transform 개념이 떠오를 것입니다.

기존 시스템의 문제점

위와 같은 기능을 제공하는 시스템이 이미 존재하기는 했습니다. 100곳 이상의 매체와의 연동을 해나가면서 도저히 매번 연동을 위한 코드 작업을 할 수는 없었기 때문입니다. 하지만 계속 더 많은 매체와 연동을 하면서 계속 추가 기능을 개발해야 할 일이 발생했습니다. 예를 들면, null을 숫자 0으로 치환해주는 함수는 미리 개발해뒀는데 새로 연동할 매체에서는 공백 문자열("")로 치환이 필요하면 새로운 함수를 추가로 개발해야 했습니다.
익숙하지 않은 DSL(Domain Specific Language)을 사용한 것도 문제였습니다. 기존 시스템에 새로운 연동을 추가하기 위해서는 DSL을 먼저 학습해야 하는 학습 비용이 들었습니다.

핵심 기술 1: Template Engine

기존 시스템의 문제를 해결하기 위해 새로운 시스템에서 사용한 핵심 기술은 바로 Template Engine이었습니다. Python에서 웹 개발을 하신 분이라면 Jinja2나 Django Template, Spring으로 웹 개발을 하신 분이라면 Thymeleaf와 같은 Template Engine 기술이 익숙하실 것입니다.
웹 페이지를 렌더링할 때 Template Engine을 사용하는 것은 이미 널리 알려진 practices입니다. HTML 페이지를 렌더링하기 위한 template을 미리 준비하고, template에 인자로 데이터들을 넣어주면 잘 변환해주는 식으로 사용합니다. 심지어 Template Engine에서는 변수 정의, 조건문, 반복문과 같은 간단한 프로그래밍 기능도 제공합니다.
Template Engine을 활용하면 위의 filter와 transform 기능을 쉽게 제공할 수 있겠다고 판단했습니다. 아래와 같은 식입니다.
1.
filter를 위한 template
Template에 조건문 등을 활용하여 조건 검사를 위한 코드를 작성합니다. 렌더링 결과를 확인하여 조건에 부합했는지 검사합니다.
2.
transform을 위한 template
원하는 포맷의 데이터를 만들 수 있도록 Template 코드를 준비합니다. 아래는 HTTP GET method 요청을 위한 URL을 template으로 표현한 예시입니다.
https://{{ API_HOST }}/limit_airbridge ?asi={{ trackingLink.click_id[0] | url_encode }} &action_id=install &...
Python
복사
url_encode 와 같은 filter(조건이 아니라 template engine 기술에서 말하는 용어) 또한 특히 유용합니다.
Airbridge에서는 Shopify나 Braze를 통해 널리 알려진 Liquid를 사용하기로 했습니다. 파트너십을 지원하는 분들에게 훨씬 더 익숙한 template 언어였기 때문입니다.

핵심 기술 2: API 호출 서버 분리

앞에서는 API 호출에 필요한 HTTP payload를 준비하는 서버에 대한 설명만 썼습니다. 하지만 Airbridge에서는 API 호출만을 담당하는 서버를 분리하여 운영하고 있는데, 그 이유는 다음과 같습니다.
1.
API 호출로 인한 서비스 지연 최소화
매체 서버에서 API 응답을 느리게 내려주는 것 때문에 다른 데이터 처리가 느려지면 안 됩니다.
2.
비동기 작업을 분리하여 리소스 활용 극대화
API 호출을 제외하면 거의 대부분 작업이 computing 집약적인 workload가 되므로 리소스 활용을 극대화할 수 있습니다.
3.
Python의 비효율성
HTTP 요청은 단순한 작업인 것에 비해 Python은 무겁습니다.

전체 시스템 아키텍처

이를 고려한 새로운 시스템의 아키텍처를 살펴보면 다음과 같습니다.
Stream Worker:
실시간 Stream 데이터를 처리한다고 하여 붙인 이름입니다. kafka로부터 이벤트 데이터를 consume 하여 template engine을 거쳐 request job을 만듭니다. 이 job들은 kafka에 저장합니다.
어느 정도 복잡한 비즈니스 로직을 구현하고 있어서 Python으로 개발되어 있습니다.
Request Worker:
API 요청만 보낸다고 하여 붙인 이름입니다. Request job들을 읽어 HTTP 요청만 처리합니다. HTTP 요청 처리 후 response code, response body 등을 log로 다시 kafka에 저장합니다.
정해진 방식대로 HTTP 요청만 처리하면 되므로 비즈니스 로직이 간단하여 Go로 개발되어 있습니다. 매우 적은 리소스만 사용하지만 많은 request들을 처리하고 있습니다.

앞으로 더 개선하고 싶은 부분들

위와 같은 아키텍처로 Daily 10억 건 이상의 event를 안정적으로 처리하고 있지만 더 개선하고 싶은 부분들도 여전히 존재합니다. Stream Worker에 대해 개선하고 싶은 부분들은 다음과 같습니다.
Graviton Instance 적용
특별한 라이브러리를 쓰고 있지 않기 때문에 비교적 쉽게 Graviton Instance를 사용할 수 있을 것으로 추측하고 있습니다. 과거 경험에 따르면 20% 이상의 비용 절감 효과가 있을 것으로 예상됩니다.
새로운 언어로 migration
Python이라서 성능에 아쉬운 점이 큽니다. 앞으로 더 많은 이벤트 데이터를 수집하게 될 텐데 이 아쉬운 부분은 점점 더 커질 것입니다. 그나마 Python 3.11 버전에서 큰 성능 향상이 있다는 것에 기대하고 있습니다.
하지만 Go나 Java와 같은 언어와 비교했을 때 Python의 성능은 여전히 아쉬움이 있습니다. 앞으로 트래픽이 늘어남에 따라 더 많은 서버를 운영할수록 비용 차이는 벌어질 것이기 때문에 언젠가는 migration 해야겠다는 계획이 있습니다.

References

ᴡʀɪᴛᴇʀ
Juhong Jung @toughrogrammer Product Div, Backend Group Lead
유니콘부터 대기업까지 쓰는 제품. 같이 만들어볼래요? 에이비일팔공에서 함께 성장할 다양한 직군의 동료들을 찾고 있어요! → 더 알아보기