Laravel Horizon, Redis 및 초간단 REST API로 퍼널 분석 엔진을 구축한 방법
Source: Dev.to
죄송합니다만, 번역을 위해서는 실제 기사 본문이 필요합니다. 위에 제공해 주신 링크의 내용을 그대로 복사해서 여기에 붙여 주시면, 해당 텍스트를 한국어로 번역해 드리겠습니다.
Source: …
핵심 문제
퍼널 분석은 간단해 보입니다: 사용자가 A를 하고, 그 다음 B를 하고, C를 한다. A에서 C까지 진행하는 비율은 얼마인가? 어디에서 이탈하는가?
실제로는 잘 구현하기가 놀라울 정도로 까다롭습니다:
- 이벤트가 비동기적으로, 순서와 관계없이 도착한다
- 퍼널은 유연해야 한다 — 단계가 다르고, 시간 프레임도 다르다
- 수천 개의 이벤트가 있어도 계산이 빨라야 한다
- 개발자를 위한 통합 부담은 최소화되어야 한다
목표: 개발자는 단일 HTTP POST 하나만으로 5 분 이내에 추적을 시작할 수 있어야 합니다.
Architecture Overview
Browser/App → REST API → Event Storage → Queue → Funnel Engine → Dashboard
Stack
- Backend: Laravel 12 with a modular architecture (
nwidart/laravel-modules) - Queue: Laravel Horizon + Redis
- Frontend: Next.js 14 + TypeScript
- Database: MariaDB
- Payments: Stripe
API 레이어
통합은 의도적으로 최소화되었습니다:
POST /api/v1/events
X-OL-Tenant-Key: your-tenant-key
X-OL-App-Key: your-app-key
Content-Type: application/json
{
"event_name": "signup_completed",
"user_identifier": "user_123",
"metadata": {
"plan": "pro",
"source": "landing_page"
}
}
두 개의 헤더와 하나의 JSON 본문—이것이 전체 계약입니다.
컨트롤러는 키를 검증하고, 테넌트와 추적된 앱을 식별한 뒤, 이벤트를 저장하고 즉시 200을 반환합니다. 요청 라이프사이클에서 무거운 작업은 없습니다.
퍼널 엔진
사용자가 대시보드에서 퍼널을 구축할 때 — 예: visited_pricing → started_trial → upgraded — 시스템은 각 단계의 전환율을 계산해야 합니다.
모든 이벤트 기록은 백그라운드 작업을 디스패치합니다:
ProcessFunnelEngineJob::dispatch($event)->onQueue('funnels');
작업은:
- 이벤트를 가져옵니다.
- 해당 앱에 대한 모든 퍼널을 로드합니다.
- 슬라이딩 윈도우 방식을 사용해 단계별 전환율을 다시 계산합니다.
- 결과를 대시보드에 캐시합니다.
큐를 사용하는 이유
- 성능: 재계산이 필요한 퍼널 수에 관계없이 API 응답이 빠르게 유지됩니다.
- 복원력: 실패한 작업은 자동으로 재시도되며, 일시적인 오류로 인한 데이터 손실이 없습니다.
Laravel Horizon은 별도 인프라 없이 작업 처리량, 실패 및 큐 깊이를 실시간으로 모니터링할 수 있는 대시보드를 제공합니다.
멀티 테넌시
Tracetics는 설계상 멀티 테넌시를 지원합니다. 각 테넌트(회사)는 여러 TrackedApps를 가질 수 있으며, 각각은 자체 이벤트와 퍼널을 가집니다. 계층 구조는 다음과 같습니다:
Tenant
└── TrackedApp (X-OL-App-Key)
└── Events
└── Funnels
└── FunnelSteps
인증은 두 개의 헤더를 사용합니다:
X-OL-Tenant-Key— 테넌트를 식별합니다X-OL-App-Key— 이벤트가 속한 앱을 식별합니다
이렇게 하면 개발자에게는 통합이 깔끔하게 유지되는 동시에 백엔드에서는 엄격한 데이터 격리를 강제합니다.
플랜 제한 및 청구
각 구독 플랜은 TrackedApps, Funnels, 그리고 월별 Events에 대한 제한을 정의합니다. 제한은 쓰기 작업이 발생하기 전에 컨트롤러 수준에서 적용됩니다:
if ($tenant->trackedApps()->count() >= $tenant->plan->app_limit) {
return response()->json(['error' => 'App limit reached'], 403);
}
Stripe는 웹훅을 통해 구독을 처리합니다. 주목할 만한 문제점: Stripe의 최신 API에서는 current_period_end를 구독 루트 객체가 아니라 items.data[0]로 이동시켰습니다—이 미묘한 변경으로 디버깅에 한 시간이 소요되었습니다.
TypeScript SDK
타입이 지정된 클라이언트를 선호하고 순수 HTTP 대신 사용하고 싶은 개발자를 위해 TypeScript SDK를 제공하고 있습니다:
npm install tracetics-sdk
import { Tracetics } from 'tracetics-sdk';
const client = new Tracetics({
tenantKey: 'your-tenant-key',
trackedAppKey: 'your-app-key',
endpoint: 'https://tracetics.com'
});
await client.track({
event_name: 'signup_completed',
user_identifier: 'user_123'
});
이 SDK는 tsup으로 빌드되며 — ESM/CJS 이중 출력, 완전한 TypeScript 타입 제공, 그리고 의존성이 전혀 없습니다.
내가 배운 것
-
쓰기 경로는 단순하고 빠르게 유지한다.
검증, 저장, 큐에 넣기. 그 외의 모든 작업은 큐에 맡긴다. -
큐 우선 아키텍처는 즉시 효과를 본다.
퍼널 계산이 API 응답을 차단하지 않으며, 관심사가 잘 분리된다. -
Stripe 웹훅은 선택 사항이 아니다.
리다이렉트 기반 확인에만 의존하면 구독 상태가 신뢰할 수 없게 된다. -
명확한 키 계층 구조가 있으면 멀티 테넌시가 더 쉬워진다.
명시적인Tenant → App → Event소유권은 권한 검사를 간단하게 만들고 데이터 격리를 완벽하게 보장한다.
다음 단계
Tracetics는 무료 플랜을 제공하며 현재 라이브 상태입니다. TypeScript SDK는 npm에 tracetics-sdk라는 이름으로 게시되어 있습니다.
비슷한 것을 구축했거나 아키텍처에 대해 질문이 있다면, 댓글에 자유롭게 의견을 공유해 주세요.