React Native FireBase Analytics 사용 준비 글에서 적어놓았듯 의견을 제시하고 기획을 제안할 때 데이터에 근거해 판단하는 문화가 도입되어야 한다고 생각했고 그 문화의 기반이 되는 데이터를 수집하기 위한 시스템을 구축하였다.
그 과정에 대한 회고 이다.
import analytics from '@react-native-firebase/analytics';
analytics().logEvent('login', { method: 'email' });
위와같이 analytics메소드를 호출 및 실행 후 return된 logEvent함수를 실행함으로서 google firebase에 이벤트기록을 남길 수 있다.
허나 이걸 그대로 사용하기에는 문제점이 두가지 있었다.
위의 두가지 문제점을 해결하기 위해 logEvent를 위한 커스텀훅을 제작해주었다.
커스텀 훅 제작에 있어서 위의 두가지문제를 아래와 같이 해결해주었다.
커스텀훅의 코드는 아래와같다.
import analytics from '@react-native-firebase/analytics';
import { Platform } from 'react-native';
import { useApolloClient } from '@apollo/client';
import { GetAccountUserQuery } from '@/graphql/auth/queries';
import { googleAnalyticsLogEventName } from '../constants/eventName/googleAnalyticsLogEventNames';
type ValuesOf<T> = T[keyof T];
const useGoogleAnalyticsLogRecord = () => {
const { cache } = useApolloClient();
const userInfoFromCache = cache.readQuery({
query: GetAccountUserQuery,
});
const logEvent = async (
eventName: ValuesOf<typeof googleAnalyticsLogEventName>,
params?: Record<string, string | number>,
) => {
try {
analytics().logEvent(eventName, {
...params,
OS: Platform.OS,
userInfo_code: `${userInfoFromCache?.accountUser.code}`,
userInfo_role: `${userInfoFromCache?.accountUser.role}`,
userInfo_nickname: `${userInfoFromCache?.accountUser.userInfo.nicknameWithCode?.nickname}`,
userInfo_nicknameCode: `${userInfoFromCache?.accountUser.userInfo.nicknameWithCode?.nicknameCode}`,
});
} catch (error) {
console.error('Failed to log event: ', error);
}
};
const logScreenView = ({ screenName }: { screenName: string }) => {
analytics().logScreenView({
screen_name: screenName,
userInfo_code: `${userInfoFromCache?.accountUser.code}`,
userInfo_role: `${userInfoFromCache?.accountUser.role}`,
userInfo_nickname: `${userInfoFromCache?.accountUser.userInfo.nicknameWithCode?.nickname}`,
userInfo_nicknameCode: `${userInfoFromCache?.accountUser.userInfo.nicknameWithCode?.nicknameCode}`,
});
};
return {
logEvent,
logScreenView,
};
};
export default useGoogleAnalyticsLogRecord;
유저정보를 가져올 때 처음엔 Get 요청을 보내 서버로부터 유저정보를 가져오는 방법을 생각하였으나
유저 정보는 처음 로그인 시 캐시에 저장되기 때문에 cache.readQuery를 통해 캐시로부터 유저정보를 가져오는 방법을 사용하여 불필요한 통신비용을 아낄 수 있었다.
위의 useGoogleAnalyticsLogRecord 커스텀훅은 고차함수로서 logEvent와 logScreenView 함수를 return해주며 아래와같이 필요 시 커스텀훅을 호출하여 return받은 함수를 사용할 수 있다.
import useGoogleAnalyticsLogRecord from '@/hooks/useGoogleAnalyticsLogRecord';
import { googleAnalyticsLogEventName } from '@/constants/eventName/googleAnalyticsLogEventNames';
const SomethingComponent = ({ setComments }: Props) => {
const { logEvent } = useGoogleAnalyticsLogRecord();
const onPressSomeButton = async () => {
logEvent(googleAnalyticsLogEventName.community_write_comment, {
param1:'param1',
param2:'param2',
.
.
.
};
return (
<SomeButton onPress={onPressSomeButton} />
);
};
export { SomethingComponent };
또한 이벤트명은 아래와같이 객체로 중앙관리하였으며 as const를 사용하여 readonly 속성을 부여하여 불의의 수정되는 사태를 방지하였습니다.
export const googleAnalyticsLogEventName = {
event_name_1: 'event_name_1',
event_name_2: 'event_name_2',
event_name_3: 'event_name_3',
event_name_4: 'event_name_4',
.
.
.
.
} as const;
유저들의 사용패턴을 파악하기 위해선 최대한 많고 촘촘하게 데이터를 수집하는게 좋으나 이 업무에만 모든시간을 할애할 수 없기에 주요 이벤트들을 특정하였다.
주요 기능들의 버튼클릭, 제출, 화면진입들의 이벤트들 위주로 수집하였다.
또한 유저가 서버에 POST 요청을 날리는 이벤트가 발생될 때에는 보안에 문제가 되지 않는선에서 유저가 날린 요청사항들도 이벤트에 기록될 수 있게 기획 및 개발을 진행하였다.
이는 추후 유저들의 행동패턴 경향을 파악하는데 큰 도움이 될거라고 생각한다.
이벤트 수집을 코딩하는 작업은 하루만에 끝낼 수 없는 작업이기에 내가 어디까지 이벤트 기록을 달았는지,
또한 이 이벤트에는 어떤 파라미터를 담기러 하였는지,
현재까지 프로덕트에 적용된 이벤트 수집은 어디까지 되었는지를 잘 기록하고 공유하는것이 중요했다.
이벤트들의 전체 목록은 노션에 기록하였으며 중간중간 현황들은 피그마를 통해 공유하였다.
피그마에 접속 시 한눈에 이벤트 수집 현황을 파악할 수 있게 색을 달리 하여 이벤트들의 현황파악을 할 수 있게 하였다.
형광으로 가득찬 모습이... 너무 짜릿해..
이번 프로젝트를 시작할때의 최종목적은 다음과 같았다.
권한 있는 사람은 누구나 데이터를 쉽게 수집할 수 있게 하여 합리적 판단의 근거를 마련해 주자
위의 목표를 달성하기 위해선 쌓여진 데이터를 누구나 쉽게 수집, 접근할 수 있는 기반을 마련해주어야한다.
데이터에 접근하기 위한 방법은 2가지를 마련하였다.
함께 SQL문을 작성해주신 GPT선생님께 감사인사 드립니다.
연결 메뉴얼은 이곳 설명에서 확인이 가능하다.
Firebase와 Bigquery의 연동은 메뉴얼도 잘 되어있고 함께 일하는 백엔드 개발자분이 도와주셔서 비교적 쉽게 완료할 수 있었다.
중요한건 데이터 테이블을 내가 원하는 양식으로 출력하는 과정이다.
빅쿼리는 SQL문을 사용하여 데이터를 출력한다.
일단 FE 개발자인 나도 SQL문에 익숙하지 않을뿐더러 비개발직군의 동료들 또한 SQL을 전혀 모르기에
알아서 잘 SQL문 입력해서 데이터 뽑아서 분석하세요.
라는 마무리는 할 수 없었다.
간단하게 SELECT, FROM, WHERE 에 대해서만 공부한 후 아래처럼 쿼리문을 작성 및 데이터를 출력해보았다.
의도와는 전혀 일치 하지 않는 출력결과를 확인하였다.
의도하는 출력된 데이터의 모양은 다음과 같다.
발생한 이벤트들이 시계열로 행단위로 나열되며
각각의 행에는 발생한 이벤트들에 대한 정보가 담겨져있는 데이터 테이블이 나의 의도이다.
일단 SELECT으로 추출한 event_params는 오브젝트이며 그 안에 각각의 이벤트들에 대한 정보를 담고 있다.
이 key-value 구조의 event_params를 일단 분리할 필요가 있었고 UNNEST를 사용하여 event_params데이터를 펴주었다.
SELECT
event_name,
event_date,
app_info.version,
param.key AS param_key,
param.value.string_value,
param.value.int_value,
param.value.float_value,
param.value.double_value
FROM
`ontol-poc-eeb84.analytics_354801107.events_*`,
UNNEST(event_params) AS param
WHERE
event_date BETWEEN '20231018' AND '20231018'
AND event_name NOT IN ('screen_view', 'user_engagement')
이 데이터를 잘 피보팅만 해주면 원하는 출력결과를 얻을 수 있었다.
COALESCE절을 통해 float, int, double, string 값 중 실제하는 값만을 param_value로 저장하였고 이렇게 UNNEST된 데이터를
WITH절을 사용하여 임시 테이블로 저장하였다.
이 후 임시 저장한 데이터에 대해 MAX(CASE WHEN ... THEN ... ELSE ... END):
이 패턴은 조건부 집계를 수행한다. 여기서 CASE 문을 사용하여 param_key 값에 따라 다른 param_value 값을 선택하고, 그 중 최대값(MAX)을 반환한다. 이 방식으로 하나의 행에 여러 param_key에 대한 값들을 모을 수 있습니다.
GROUP BY 절을 사용하여 event_name과 formatted_timestamp로 그룹화하면 하나의 event_name행에 여러 parma_key에 대한 값들을 모을 수 있다.
WITH unnested_params AS (
SELECT
event_name,
FORMAT_TIMESTAMP("%Y%m%d%H%M%S", TIMESTAMP_MICROS(event_timestamp)) AS formatted_timestamp,
param.key AS param_key,
COALESCE(
CAST(param.value.int_value AS STRING),
CAST(param.value.float_value AS STRING),
CAST(param.value.double_value AS STRING),
param.value.string_value
) AS param_value
FROM
`ontol-poc-eeb84.analytics_354801107.events_*`,
UNNEST(event_params) AS param
WHERE
TIMESTAMP_MICROS(event_timestamp) BETWEEN TIMESTAMP("2023-10-14 00:00:00 UTC") AND TIMESTAMP("2023-10-23 23:59:59 UTC")
AND event_name NOT IN ('screen_view', 'user_engagement')
)
SELECT
event_name,
formatted_timestamp,
MAX(CASE WHEN param_key = 'key1' THEN param_value ELSE NULL END) AS key1_value,
MAX(CASE WHEN param_key = 'key2' THEN param_value ELSE NULL END) AS key2_value,
MAX(CASE WHEN param_key = 'key3' THEN param_value ELSE NULL END) AS key3_value,
FROM
unnested_params
GROUP BY
event_name,
formatted_timestamp
ORDER BY
formatted_timestamp, event_name
위와같이 GPT선생님과 함께 쿼리문을 완성할 수 있엇다.
이제 이 쿼리문을 저장한 후 데이터가 필요한 사람은 아래의이미지의 기간에 해당하는 부분만 원하는 기간으로 변경만 하면 원하는 기간동안의 이벤트 데이터를 수집할 수 있게 되다.
추가적으로 누구든 이용할 수 있는 방법을 쉽게 알 수 있도록 메뉴얼을 별도로 작성하여 공유하였다.
데일리로 루틴하게 보는 지표들은 굳이 쿼리조회가 아니라 언제든 쉽게 조회할 수 있는 방법이 필요했다.
Looker Studio를 사용하여 DB 혹은 Firebase와 연동하면 실시간으로 쌓여가는 데이터를 원하는 형식으로
확인할 수 있다.
아래는 특정 데이터의 트렌드를 그린 차트이며 일정 주기로 실시간 업데이트가 된다.
이제 사내 인원 누구든 유저들의 행동패턴 데이터에 대해 쉽게 접근할 수 있게되었다.
이를 기반으로 더 이상 의견을 결정할 때 누군가의 감이나, 혹은 느낌이 아닌 정확한 데이터를 기반으로 결정이 이루어졌으면 하는 바람이다.
.