티클 서비스에 대한 설명은 아래 앱스토어 플레이스토어에서 확인이 가능하다
TIKKLE(티클) - 기억에 남는 프리미엄 선물 - Apps on AppStore
TIKKLE(티클) - 기억에 남는 프리미엄 선물 - Apps on Google Play
우선 크게 아래 4가지 카테고리로 나누었다.
1. Funnel Level별 사용자 행동(사용자가 앱 설치 이후 어느단계까지 진입하고 각 단계마다 비율이 얼마나 줄어드는지 확인 위함)
2. 광고 매체 효과 측정을 위한 지표
3. 성과 측정을 위한 지표
4. 기능 개선을 위한 사용자 행동지표
5. 알고리즘 학습을 위한 EVENT
페이스북에서는 앱내에서 유발시키는 이벤트를 통해서 광고알고리즘을 학습하고 해당 이벤트를 일으키는 사용자에게 더 많은 광고를 노출시킬 수 있다.
아래 항목은 이러한 기능을 위해 필요한 이벤트를 정리한 것이다.
본격적인 구현 전에 2019 NEXON Developers Conference(2019. 4. 24 ~ 26)의 아래 자료를 참고하고 공부했엇다.
(자막)[NDC19] 좋은 로그란 무엇인가?: 좋은 로그를 위해 고려해야 할 것들
백엔드에서 구현할지 프론트에서 구현할지 두가지 선택지가 있었다.
각각의 장단점을 정리해보면
프론트에서 구현했을 때
장점
단점
백엔드에서 구현했을 때
장점
단점
위 장단점을 고려하고 현재 서비스에서 다른 요구사항들이 많다는 것을 고려했을때 중요한 가치는 적은 리소스로 빠르고 정확하게 핵심적인 부분들만 로깅하는 것이었고 그렇기에 백엔드에서 구현하였다.
CREATE TABLE funnel_action (
id int NOT NULL,
description varchar(255) NOT NULL,
level int NOT NULL,
PRIMARY KEY(`id`)
);
INSERT INTO funnel_action (id, description, level) VALUES (1, '회원가입, 로그인', 1);
INSERT INTO funnel_action (id, description, level) VALUES (2, '홈화면 이동', 1);
INSERT INTO funnel_action (id, description, level) VALUES (3, '상품탭으로 이동', 2);
INSERT INTO funnel_action (id, description, level) VALUES (4, '상품 상세 페이지로 이동', 2);
INSERT INTO funnel_action (id, description, level) VALUES (5, '상품 검색', 2);
INSERT INTO funnel_action (id, description, level) VALUES (6, '티클링 시작', 3);
INSERT INTO funnel_action (id, description, level) VALUES (7, '첫 티클 수령 ', 4);
INSERT INTO funnel_action (id, description, level) VALUES (8, '티클링 구매 및 전송', 4);
CREATE TABLE funnel_log(
id int NOT NULL AUTO_INCREMENT,
user_id int NOT NULL,
funnel_action_id int NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(`id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
FOREIGN KEY (`funnel_action_id`) REFERENCES `funnel_action` (`id`)
);
그리고 아래의 sql문을통해 각 funnel단계의 사용자수의 감소를 확인할 수 있었다.
SELECT
fa.id AS FunnelActionID,
fa.description AS ActionDescription,
COUNT(DISTINCT fl.user_id) AS UserCount
FROM
funnel_log fl
JOIN
funnel_action fa ON fl.funnel_action_id = fa.id
GROUP BY
fa.id, fa.description
ORDER BY
fa.id;
구현 코드는 아래와 같다.
ActionFunnelLogger를 만들어 해당하는 api 6개 정도에 해당 함수가 미들웨어로 들어가도록 구성하였다.
api.post("/tikkling/create", authtoken, actionFunnelLogger, post_tikkling_create);
const { ActionFunnelLogManager } = require("./features/ActionFunnelLogManager");
const { DBManager } = require("./db");
exports.actionFunnelLogger = async (req, res, next) => {
const db = new DBManager();
await db.openTransaction();
try {
const user_id = req.id;
//로그 매니저 객체 생성
const funnel_log_manager = await ActionFunnelLogManager.createActionFunnelLogManager({ user_id, db });
//로그 매니저 객체로 해당 api의 이름을 전달
await funnel_log_manager.logControllInterface(req.route);
await db.commitTransaction();
} catch (error) {
await db.rollbackTransaction();
//return invalid when token is invalid
console.error(`🚨 error -> ⚡️ actionFunnelLogger : 🐞${error}`);
}
next();
};
async logControllInterface(route) {
try {
switch (route) {
case "/post_auth_registerUser":
if (this.signup == false) {
this.signup = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 1]);
}
break;
case "/get_user_myWishlist":
if (this.move_to_home == false) {
this.move_to_tikkling = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 2]);
}
break;
case "/post_product_list":
if (this.move_to_product == false) {
this.move_to_home = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 3]);
}
break;
case "/post_product_info":
if (this.move_to_product_detail == false) {
this.move_to_product_detail = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 4]);
}
break;
case "/post_product_search":
if (this.search_product == false) {
this.search_product = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 5]);
}
break;
case "/post_tikkling_create":
if (this.start_tikkling == false) {
this.start_tikkling = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 6]);
}
break;
case "/post_payment_init/:tikkleAction":
if (this.send_tikkle == false) {
this.send_tikkle = true;
await this.db.executeQuery(`INSERT INTO funnel_log (user_id, funnel_action_id) VALUES (?, ?)`, [this.user_id, 8]);
}
break;
default:
break;
}
} catch (error) {
console.error(`🚨 error -> ⚡️ logControllInterface : 🐞 ${error}`);
throw new ExpectedError(error);
}
+위 구현에서 좀 부족한 부분이나 개선시킬 수 있는 부분이 있다면 모두 알려주세요!
진입 후 상품 탭으로 이동하지 않는 유저가 존재하여 홈화면에서 이를 위한 CTA를 추가하였고 위에서 확인할 수 있듯 거의 모든 유저가 상품탭까지 이동하도록 행동을 유도하였다.
상품탭 이동 이후 티클링 오픈의 수치가 1/3 수준으로 작아져 이를 해결하기위해 기존에 티클링 생성시 기간을 설정을 제거하였고 티클링 생성 페이지를 제거하여 티클링 생성까지의 단계를 줄였다. 이후 해당 지표가 확실히 개선된 것을 확인할 수 있었다.(기존에는 티클링 시작 페이지에서 날짜, 주소등을 설정하여야했는데 날짜 설정을 제거하고 주소는 배송을 받기 직전에 설정하는 쪽으로 이동시켜 티클링 생성 페이지를 제거하였다.
기존
변경 후
그 외에 사람들이 공유까지 이어지는 것이 미미하여 티클링 오픈 화면에서 CTA를 만들어 좀 더 공유를 유도하려고하였고 이것이 지표로서 확인되지는 않았지만 티클링 공유로 가입하는유저의 수치가 유의미하게 증가한 것을 확인할 수 있었다.
기존
변경 후
)
이후 시각화까지 하여 글을 마저 써보려고 한다.
시각화 툴에는 아래와 같은 툴 들이 있다고 하는데 이를 사용해본 후에 해당 글을 마저 채울듯하다.
Tableau: 사용자 친화적인 인터페이스와 강력한 시각화 기능을 제공하는 도구이다. SQL 데이터베이스에 직접 연결하여 데이터를 분석하고 시각화할 수 있다.
Microsoft Power BI: 데이터 분석 및 시각화를 위한 Microsoft의 도구로, SQL 서버와 같은 다양한 데이터 소스를 지원.
Looker: 데이터를 탐색하고 시각화할 수 있는 비즈니스 인텔리전스 플랫폼입니다. SQL 지식이 필요하지만 매우 강력한 도구.
QlikView/Qlik Sense: 사용자가 데이터를 쉽게 탐색하고 분석할 수 있게 해주는 시각화 도구입니다.
Metabase: 오픈 소스 SQL 시각화 도구로, 비개발자도 쉽게 사용할 수 있도록 설계.
Redash: 또 다른 오픈 소스 시각화 도구로, 데이터베이스와의 연결을 통해 쿼리를 실행하고 결과를 시각화할 수 있다.