[TIKKLE] 서비스 개선기, 로깅 작업

최영섭·2023년 12월 24일
0
post-thumbnail


티클 서비스에 대한 설명은 아래 앱스토어 플레이스토어에서 확인이 가능하다
TIKKLE(티클) - 기억에 남는 프리미엄 선물 - Apps on AppStore
TIKKLE(티클) - 기억에 남는 프리미엄 선물 - Apps on Google Play

0. 배경 및 요구사항

  • 크라우드 펀딩 선물 서비스 티클을 개발 하고 배포하였다.(현재 android, ios에서 확인 가능합니다!)
  • 티클을 배포하고 난 뒤 사용자들이 어떻게 해당 서비스를 활용하는지 확인하고 문제를 진단할 수 있어야함(ex. 로그인 과정에서는 어디에서 가장 많이 이탈하는지, 회원가입 한 유저 중 얼마나 많은 유저가 상품 페이지에 들어가서 상품을 확인하는지 등)
  • 이를 위해서 사용자 funnel을 구성하고 이를 기록해야할 필요성이 생김

1. 티클 로깅 항목 구성

우선 크게 아래 4가지 카테고리로 나누었다.
1. Funnel Level별 사용자 행동(사용자가 앱 설치 이후 어느단계까지 진입하고 각 단계마다 비율이 얼마나 줄어드는지 확인 위함)
2. 광고 매체 효과 측정을 위한 지표
3. 성과 측정을 위한 지표
4. 기능 개선을 위한 사용자 행동지표
5. 알고리즘 학습을 위한 EVENT

1) Funnel Level별 사용자 행동

2) 광고 매체효과 측정을 위한 지표


3) 성과 측정을 위한 지표

4) 기능 개선을 위한 사용자 행동지표


5) 알고리즘 학습을 위한 이벤트

페이스북에서는 앱내에서 유발시키는 이벤트를 통해서 광고알고리즘을 학습하고 해당 이벤트를 일으키는 사용자에게 더 많은 광고를 노출시킬 수 있다.
아래 항목은 이러한 기능을 위해 필요한 이벤트를 정리한 것이다.

2. 구현 방법

본격적인 구현 전에 2019 NEXON Developers Conference(2019. 4. 24 ~ 26)의 아래 자료를 참고하고 공부했엇다.
(자막)[NDC19] 좋은 로그란 무엇인가?: 좋은 로그를 위해 고려해야 할 것들

1) 각 방식의 장단점

백엔드에서 구현할지 프론트에서 구현할지 두가지 선택지가 있었다.
각각의 장단점을 정리해보면

프론트에서 구현했을 때
장점

  • 사용자의 경로, 행동을 좀 더 상세하게 로깅할 수 있음

단점

  • 추가적으로 api를 만들고 따로 셋업이 필요, 좀 더 많은 리소스
  • 한 곳에서 로깅을 관리할 수 없고 여러 곳에서 관리해야함

백엔드에서 구현했을 때
장점

  • api의 미들웨어로 구현했을때 한곳에서 로깅을 관리할 수 있음
  • 좀 더 적을 리소스로 작업할 수 있음

단점

  • 차후 이런 방식으로 로깅하는 것들이 많아진다면 성능저하를 일으킬 수 있음
  • 유저의 상세한 동작을 로깅할 수는 없음

위 장단점을 고려하고 현재 서비스에서 다른 요구사항들이 많다는 것을 고려했을때 중요한 가치는 적은 리소스로 빠르고 정확하게 핵심적인 부분들만 로깅하는 것이었고 그렇기에 백엔드에서 구현하였다.

2) DB구성

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;

3) 구현 코드

구현 코드는 아래와 같다.
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();
};
  1. 객체를 생성한 시점에 db상의 유저의 기존 로그를 가져온 뒤 객체에 반영한다.
  2. 이후 locgContollInterface에 api의 이름을 전달하게 된 후 함수 내에서 어떤 로그인지 판단하고 해당 로그를 기록한다.
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);
    }

+위 구현에서 좀 부족한 부분이나 개선시킬 수 있는 부분이 있다면 모두 알려주세요!

3. 서비스 개선

1) 상품 탭이동

진입 후 상품 탭으로 이동하지 않는 유저가 존재하여 홈화면에서 이를 위한 CTA를 추가하였고 위에서 확인할 수 있듯 거의 모든 유저가 상품탭까지 이동하도록 행동을 유도하였다.

2) 티클링 오픈

상품탭 이동 이후 티클링 오픈의 수치가 1/3 수준으로 작아져 이를 해결하기위해 기존에 티클링 생성시 기간을 설정을 제거하였고 티클링 생성 페이지를 제거하여 티클링 생성까지의 단계를 줄였다. 이후 해당 지표가 확실히 개선된 것을 확인할 수 있었다.(기존에는 티클링 시작 페이지에서 날짜, 주소등을 설정하여야했는데 날짜 설정을 제거하고 주소는 배송을 받기 직전에 설정하는 쪽으로 이동시켜 티클링 생성 페이지를 제거하였다.
기존

변경 후

3) 티클링 공유

그 외에 사람들이 공유까지 이어지는 것이 미미하여 티클링 오픈 화면에서 CTA를 만들어 좀 더 공유를 유도하려고하였고 이것이 지표로서 확인되지는 않았지만 티클링 공유로 가입하는유저의 수치가 유의미하게 증가한 것을 확인할 수 있었다.
기존

변경 후

)

4. 시각화

이후 시각화까지 하여 글을 마저 써보려고 한다.
시각화 툴에는 아래와 같은 툴 들이 있다고 하는데 이를 사용해본 후에 해당 글을 마저 채울듯하다.

  • Tableau: 사용자 친화적인 인터페이스와 강력한 시각화 기능을 제공하는 도구이다. SQL 데이터베이스에 직접 연결하여 데이터를 분석하고 시각화할 수 있다.

  • Microsoft Power BI: 데이터 분석 및 시각화를 위한 Microsoft의 도구로, SQL 서버와 같은 다양한 데이터 소스를 지원.

  • Looker: 데이터를 탐색하고 시각화할 수 있는 비즈니스 인텔리전스 플랫폼입니다. SQL 지식이 필요하지만 매우 강력한 도구.

  • QlikView/Qlik Sense: 사용자가 데이터를 쉽게 탐색하고 분석할 수 있게 해주는 시각화 도구입니다.

  • Metabase: 오픈 소스 SQL 시각화 도구로, 비개발자도 쉽게 사용할 수 있도록 설계.

  • Redash: 또 다른 오픈 소스 시각화 도구로, 데이터베이스와의 연결을 통해 쿼리를 실행하고 결과를 시각화할 수 있다.

profile
세상에 필요한 것을 고민하고 그것을 만드는 과정에서 문제를 해결하는 일이 즐겁습니다. 창업, 백엔드, RAG에 관심을 가지고있습니다.

0개의 댓글