created plan for a collector microservice
This commit is contained in:
@@ -0,0 +1,843 @@
|
|||||||
|
# Analytics Collector App Extraction Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines the plan for extracting the data collection endpoints from the main analytics application into a separate Laravel application. The collector app will be responsible for receiving tracking data from external websites, processing it, and storing it in the **shared database** (same database as the main app).
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. **Separation of Concerns**: Isolate high-traffic data collection endpoints from the analytics dashboard
|
||||||
|
2. **Scalability**: Enable independent scaling of the collector service
|
||||||
|
3. **Reliability**: Prevent tracking service disruptions from affecting dashboard availability
|
||||||
|
4. **Performance**: Optimize collector for high-throughput, low-latency operations
|
||||||
|
5. **Security**: Isolate public-facing endpoints from internal analytics data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Single Laravel Application │
|
||||||
|
│ ┌─────────────┐ ┌────────────────┐ │
|
||||||
|
│ │ Dashboard │ │ Collection │ │
|
||||||
|
│ │ (Auth UI) │ │ Endpoints │ │
|
||||||
|
│ └─────────────┘ └────────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └───────┬───────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌─────▼─────┐ │
|
||||||
|
│ │ Database │ │
|
||||||
|
│ └───────────┘ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Target State (Shared Database Architecture)
|
||||||
|
```
|
||||||
|
┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ Analytics App │ │ Collector App │
|
||||||
|
│ (Dashboard/Auth) │ │ (Data Ingestion) │
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌────────────────┐ │ │ ┌────────────────┐ │
|
||||||
|
│ │ Reports/UI │ │ │ │ POST /api/e │ │
|
||||||
|
│ │ User Mgmt │ │ │ │ GET /api/p │ │
|
||||||
|
│ │ Settings │ │ │ │ Tracking JS │ │
|
||||||
|
│ └────────────────┘ │ │ └────────────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ └───────────┴───────┴──────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌───────▼────────┐ │
|
||||||
|
│ │ Shared Database│ │
|
||||||
|
│ │ (All tables) │ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Both apps read/write to the same database:
|
||||||
|
- No data sync needed
|
||||||
|
- Real-time data availability
|
||||||
|
- Simpler architecture
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Setup New Collector App
|
||||||
|
|
||||||
|
### 1.1 Create New Laravel Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer create-project laravel/laravel analytics-collector
|
||||||
|
cd analytics-collector
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Configure Environment
|
||||||
|
|
||||||
|
**`.env` configuration for collector app:**
|
||||||
|
```env
|
||||||
|
APP_NAME="Analytics Collector"
|
||||||
|
|
||||||
|
# IMPORTANT: Same database as main app
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1 # Same as main app
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=analytics # Same database name as main app
|
||||||
|
DB_USERNAME=same_user_as_main_app
|
||||||
|
DB_PASSWORD=same_password_as_main_app
|
||||||
|
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
REDIS_CLIENT=phpredis
|
||||||
|
|
||||||
|
# Rate limiting - can be higher than main app
|
||||||
|
COLLECTOR_RATE_LIMIT=120 # per minute per IP
|
||||||
|
|
||||||
|
# Geolocation
|
||||||
|
LOCATION_TESTING=false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- Database credentials must match the main app exactly
|
||||||
|
- Both apps will share all tables (websites, visitors, sessions, events, etc.)
|
||||||
|
- No separate database needed
|
||||||
|
- No data sync mechanism required
|
||||||
|
|
||||||
|
### 1.3 Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require jaybizzle/crawler-detect
|
||||||
|
composer require stevebauman/location
|
||||||
|
composer require predis/predis
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Database Setup (Shared Database)
|
||||||
|
|
||||||
|
### 2.1 No Migrations Needed!
|
||||||
|
|
||||||
|
Since both apps use the **same database**, there's no need to copy or create migrations in the collector app.
|
||||||
|
|
||||||
|
**What this means:**
|
||||||
|
- The database already has all necessary tables from the main app
|
||||||
|
- The collector app will simply connect to the existing database
|
||||||
|
- Both apps read/write to the same tables directly
|
||||||
|
- No schema changes required
|
||||||
|
|
||||||
|
### 2.2 Skip Running Migrations
|
||||||
|
|
||||||
|
**Do NOT run migrations on the collector app** - the tables already exist from the main app.
|
||||||
|
|
||||||
|
Instead:
|
||||||
|
1. Configure `.env` to point to the same database
|
||||||
|
2. Test database connection: `php artisan db:show`
|
||||||
|
3. Verify tables exist: `php artisan tinker` → `DB::table('websites')->count()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Code Extraction
|
||||||
|
|
||||||
|
### 3.1 Controllers to Copy
|
||||||
|
|
||||||
|
**File: `app/Http/Controllers/CollectController.php`**
|
||||||
|
|
||||||
|
Copy entire controller with modifications:
|
||||||
|
- Keep all validation logic
|
||||||
|
- Keep bot detection (`isBot()`, `isDomainValid()`)
|
||||||
|
- Dispatch `ProcessTrackingPayload` job
|
||||||
|
|
||||||
|
**File: `app/Http/Controllers/TrackingScriptController.php`**
|
||||||
|
|
||||||
|
Copy entire controller:
|
||||||
|
- Serves JavaScript tracking code
|
||||||
|
- Update collection endpoint URLs to point to collector app
|
||||||
|
|
||||||
|
### 3.2 Middleware to Copy
|
||||||
|
|
||||||
|
**File: `app/Http/Middleware/CollectCors.php`**
|
||||||
|
|
||||||
|
Copy entire middleware for CORS handling.
|
||||||
|
|
||||||
|
**Register in `bootstrap/app.php`:**
|
||||||
|
```php
|
||||||
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
$middleware->alias([
|
||||||
|
'collect.cors' => \App\Http\Middleware\CollectCors::class,
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Jobs to Copy
|
||||||
|
|
||||||
|
**File: `app/Jobs/ProcessTrackingPayload.php`**
|
||||||
|
|
||||||
|
Copy entire job with modifications:
|
||||||
|
- Keep all visitor/session/pageview/event processing
|
||||||
|
- Keep goal evaluation logic
|
||||||
|
- Add error handling for missing websites (sync issue)
|
||||||
|
|
||||||
|
### 3.4 Models to Copy
|
||||||
|
|
||||||
|
Copy the following models **exactly as they are** from the main app:
|
||||||
|
- `app/Models/Website.php`
|
||||||
|
- `app/Models/Visitor.php`
|
||||||
|
- `app/Models/AnalyticsSession.php`
|
||||||
|
- `app/Models/PageView.php`
|
||||||
|
- `app/Models/Event.php`
|
||||||
|
- `app/Models/Goal.php`
|
||||||
|
- `app/Models/GoalConversion.php`
|
||||||
|
|
||||||
|
**Important notes:**
|
||||||
|
- Copy the models unchanged - they need to work with the same database schema
|
||||||
|
- Keep all relationships intact
|
||||||
|
- Keep all casts and accessors
|
||||||
|
- The only exception: You can remove the `User` relationship from `Website.php` if the collector doesn't need it
|
||||||
|
|
||||||
|
### 3.5 Services to Copy
|
||||||
|
|
||||||
|
**File: `app/Services/GoalEvaluationService.php`**
|
||||||
|
|
||||||
|
Copy entire service for goal evaluation logic.
|
||||||
|
|
||||||
|
### 3.6 Views to Copy
|
||||||
|
|
||||||
|
**File: `resources/views/tracking/tracker.blade.php`**
|
||||||
|
|
||||||
|
Copy JavaScript tracking code with modifications:
|
||||||
|
- Update collection endpoint URLs to collector app
|
||||||
|
- Keep all tracking logic (visitor ID, session management, event tracking)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Routes Configuration
|
||||||
|
|
||||||
|
### 4.1 API Routes
|
||||||
|
|
||||||
|
**File: `routes/api.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Http\Controllers\CollectController;
|
||||||
|
use App\Http\Controllers\TrackingScriptController;
|
||||||
|
|
||||||
|
// Main collection endpoints (backward compatible)
|
||||||
|
Route::post('/e', [CollectController::class, 'store'])
|
||||||
|
->middleware(['collect.cors', 'throttle:collect'])
|
||||||
|
->name('collect.store');
|
||||||
|
|
||||||
|
Route::get('/p', [CollectController::class, 'pixel'])
|
||||||
|
->middleware(['collect.cors', 'throttle:collect'])
|
||||||
|
->name('collect.pixel');
|
||||||
|
|
||||||
|
// Ad-blocker resistant endpoints
|
||||||
|
Route::post('/cdn/feedback', [CollectController::class, 'store'])
|
||||||
|
->middleware(['collect.cors', 'throttle:collect'])
|
||||||
|
->name('collect.store.alt');
|
||||||
|
|
||||||
|
Route::get('/cdn/status.gif', [CollectController::class, 'pixel'])
|
||||||
|
->middleware(['collect.cors', 'throttle:collect'])
|
||||||
|
->name('collect.pixel.alt');
|
||||||
|
|
||||||
|
// Tracking script delivery
|
||||||
|
Route::get('/t/{trackingId}.js', [TrackingScriptController::class, 'script'])
|
||||||
|
->middleware('collect.cors')
|
||||||
|
->name('tracking.script');
|
||||||
|
|
||||||
|
Route::get('/cdn/resources/{trackingId}.js', [TrackingScriptController::class, 'script'])
|
||||||
|
->middleware('collect.cors')
|
||||||
|
->name('tracking.script.alt');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Rate Limiting Configuration
|
||||||
|
|
||||||
|
**File: `app/Providers/AppServiceProvider.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
RateLimiter::for('collect', function (Request $request) {
|
||||||
|
return Limit::perMinute(config('collector.rate_limit', 120))
|
||||||
|
->by($request->ip());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create config file: `config/collector.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
return [
|
||||||
|
'rate_limit' => env('COLLECTOR_RATE_LIMIT', 120),
|
||||||
|
'main_app_url' => env('MAIN_APP_URL'),
|
||||||
|
'api_key' => env('COLLECTOR_API_KEY'),
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: No Sync Mechanism Needed! 🎉
|
||||||
|
|
||||||
|
### 5.1 Shared Database = No Sync Required
|
||||||
|
|
||||||
|
Since both apps use the **same database**, there's **NO need for a sync mechanism**!
|
||||||
|
|
||||||
|
**What this means:**
|
||||||
|
- When a website is created/updated/deleted in the main app, it's immediately available in the collector app
|
||||||
|
- When a goal is created/updated/deleted in the main app, the collector app sees it instantly
|
||||||
|
- When the collector creates tracking data, the main app can query it immediately
|
||||||
|
- Zero latency between apps
|
||||||
|
- No complex sync logic needed
|
||||||
|
- No API endpoints for syncing
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Simpler codebase (no sync controllers, middleware, or API calls)
|
||||||
|
- Real-time data consistency
|
||||||
|
- No sync delays or failures to handle
|
||||||
|
- Reduced network overhead
|
||||||
|
- Fewer moving parts = fewer potential issues
|
||||||
|
|
||||||
|
**Only consideration:**
|
||||||
|
- Both apps must have read access to `websites` and `goals` tables
|
||||||
|
- Both apps need write access to `visitors`, `sessions`, `page_views`, `events`, `goal_conversions` tables
|
||||||
|
- Database connection pooling should account for connections from both apps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Data Flow - Already Solved! ✅
|
||||||
|
|
||||||
|
### Shared Database Architecture
|
||||||
|
|
||||||
|
**Data flow is automatic with shared database:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Collector App Main App
|
||||||
|
│ │
|
||||||
|
│ Writes tracking data │ Reads tracking data
|
||||||
|
│ (visitors, sessions, │ (for reports & analytics)
|
||||||
|
│ page_views, events) │
|
||||||
|
│ │
|
||||||
|
└──────────┬───────────────────┘
|
||||||
|
│
|
||||||
|
┌──────▼───────┐
|
||||||
|
│ Database │
|
||||||
|
│ (Shared) │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. **Collector writes** → tracking data (visitors, sessions, page_views, events, conversions)
|
||||||
|
2. **Main app reads** → same tables for reports, dashboards, analytics
|
||||||
|
3. **Main app writes** → configuration data (websites, goals, users, settings)
|
||||||
|
4. **Collector reads** → website/goal configs for validation and goal evaluation
|
||||||
|
|
||||||
|
**No additional implementation needed** - this is the beauty of the shared database approach!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Deployment Strategy
|
||||||
|
|
||||||
|
### 7.1 Infrastructure Setup
|
||||||
|
|
||||||
|
**Collector App Requirements:**
|
||||||
|
- High-availability load balancer
|
||||||
|
- Multiple app instances for horizontal scaling
|
||||||
|
- Redis for queue and cache
|
||||||
|
- Database (MySQL/PostgreSQL)
|
||||||
|
- CDN for tracking script delivery (optional)
|
||||||
|
|
||||||
|
**Suggested Stack:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ CDN (CloudFlare) │
|
||||||
|
│ /cdn/resources/*.js │
|
||||||
|
└────────┬────────────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────────────┐
|
||||||
|
│ Load Balancer │
|
||||||
|
│ (nginx/HAProxy) │
|
||||||
|
└────────┬────────────────┘
|
||||||
|
│
|
||||||
|
┌────┴────┐
|
||||||
|
│ │
|
||||||
|
┌───▼───┐ ┌──▼────┐
|
||||||
|
│ App 1 │ │ App 2 │ ... (N instances)
|
||||||
|
└───┬───┘ └──┬────┘
|
||||||
|
│ │
|
||||||
|
┌───▼────────▼───┐
|
||||||
|
│ Redis Queue │
|
||||||
|
└───┬────────────┘
|
||||||
|
│
|
||||||
|
┌───▼────────────┐
|
||||||
|
│ Queue Workers │
|
||||||
|
└───┬────────────┘
|
||||||
|
│
|
||||||
|
┌───▼────────────┐
|
||||||
|
│ Database │
|
||||||
|
└────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Migration Steps
|
||||||
|
|
||||||
|
**Step 1: Prepare Collector App**
|
||||||
|
1. Deploy collector app to staging
|
||||||
|
2. Run migrations
|
||||||
|
3. Sync initial website/goal data
|
||||||
|
4. Test collection endpoints
|
||||||
|
|
||||||
|
**Step 2: Gradual Rollout**
|
||||||
|
1. Update tracking script to include feature flag
|
||||||
|
2. Route 10% of traffic to collector app
|
||||||
|
3. Monitor error rates, latency, data accuracy
|
||||||
|
4. Gradually increase to 100%
|
||||||
|
|
||||||
|
**Step 3: DNS/Routing Changes**
|
||||||
|
1. Update tracking script URLs to collector domain
|
||||||
|
2. Setup CNAME: `track.yourdomain.com` → collector app
|
||||||
|
3. Update CORS configuration
|
||||||
|
|
||||||
|
**Step 4: Main App Cleanup**
|
||||||
|
1. Remove collection routes from main app
|
||||||
|
2. Remove `CollectController`, `TrackingScriptController`
|
||||||
|
3. Keep models and migrations (if shared database)
|
||||||
|
4. Archive old code for rollback safety
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Monitoring & Observability
|
||||||
|
|
||||||
|
### 8.1 Metrics to Track
|
||||||
|
|
||||||
|
**Collector App:**
|
||||||
|
- Request rate (req/sec)
|
||||||
|
- Response time (p50, p95, p99)
|
||||||
|
- Error rate by type (bot, validation, domain_mismatch)
|
||||||
|
- Queue depth and processing rate
|
||||||
|
- Database connection pool usage
|
||||||
|
- Bot traffic percentage
|
||||||
|
|
||||||
|
**Dashboard:**
|
||||||
|
```php
|
||||||
|
// app/Http/Controllers/HealthController.php
|
||||||
|
public function stats(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'requests_last_minute' => Cache::get('requests_last_minute', 0),
|
||||||
|
'queue_depth' => Queue::size(),
|
||||||
|
'db_connections' => DB::connection()->getDoctrineConnection()->getWrappedConnection()->getAttribute(PDO::ATTR_SERVER_INFO),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Alerting
|
||||||
|
|
||||||
|
Set up alerts for:
|
||||||
|
- Response time > 200ms (p95)
|
||||||
|
- Error rate > 1%
|
||||||
|
- Queue depth > 10,000
|
||||||
|
- Database connections > 80% of pool
|
||||||
|
- Disk space < 20%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9: Performance Optimizations
|
||||||
|
|
||||||
|
### 9.1 Database Optimizations
|
||||||
|
|
||||||
|
**Indexing Strategy:**
|
||||||
|
```sql
|
||||||
|
-- Visitors table
|
||||||
|
CREATE INDEX idx_visitors_lookup ON visitors(website_id, uid);
|
||||||
|
CREATE INDEX idx_visitors_last_seen ON visitors(website_id, last_seen_at);
|
||||||
|
|
||||||
|
-- Sessions table
|
||||||
|
CREATE INDEX idx_sessions_lookup ON analytics_sessions(website_id, uid);
|
||||||
|
CREATE INDEX idx_sessions_started ON analytics_sessions(website_id, started_at);
|
||||||
|
|
||||||
|
-- Page views table
|
||||||
|
CREATE INDEX idx_pageviews_viewed ON page_views(website_id, viewed_at);
|
||||||
|
CREATE INDEX idx_pageviews_path ON page_views(website_id, path(255));
|
||||||
|
|
||||||
|
-- Events table
|
||||||
|
CREATE INDEX idx_events_occurred ON events(website_id, occurred_at);
|
||||||
|
CREATE INDEX idx_events_name ON events(website_id, name);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Partitioning (for very high volume):**
|
||||||
|
```sql
|
||||||
|
-- Partition page_views by month
|
||||||
|
ALTER TABLE page_views PARTITION BY RANGE (YEAR(viewed_at) * 100 + MONTH(viewed_at)) (
|
||||||
|
PARTITION p202603 VALUES LESS THAN (202604),
|
||||||
|
PARTITION p202604 VALUES LESS THAN (202605),
|
||||||
|
-- ... add future partitions
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Queue Optimization
|
||||||
|
|
||||||
|
**Use Redis for queue:**
|
||||||
|
```env
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
REDIS_QUEUE=tracking
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tune worker count based on load:**
|
||||||
|
```bash
|
||||||
|
php artisan queue:work redis --queue=tracking --sleep=1 --tries=3 --max-jobs=1000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Horizon for queue management:**
|
||||||
|
```bash
|
||||||
|
composer require laravel/horizon
|
||||||
|
php artisan horizon:install
|
||||||
|
php artisan horizon
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 Caching Strategy
|
||||||
|
|
||||||
|
**Cache website configs:**
|
||||||
|
```php
|
||||||
|
// In ProcessTrackingPayload job
|
||||||
|
protected function getWebsite(string $trackingId): ?Website
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
"website:{$trackingId}",
|
||||||
|
now()->addHours(1),
|
||||||
|
fn() => Website::where('tracking_id', $trackingId)->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cache goals:**
|
||||||
|
```php
|
||||||
|
protected function getActiveGoals(int $websiteId): Collection
|
||||||
|
{
|
||||||
|
return Cache::remember(
|
||||||
|
"goals:{$websiteId}",
|
||||||
|
now()->addMinutes(5),
|
||||||
|
fn() => Goal::where('website_id', $websiteId)
|
||||||
|
->where('is_active', true)
|
||||||
|
->get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10: Security Considerations
|
||||||
|
|
||||||
|
### 10.1 DDoS Protection
|
||||||
|
|
||||||
|
**Rate Limiting by IP:**
|
||||||
|
- Current: 120 req/min per IP
|
||||||
|
- Consider: Dynamic rate limiting based on traffic patterns
|
||||||
|
- Use CloudFlare or similar CDN for DDoS protection
|
||||||
|
|
||||||
|
**Bot Detection:**
|
||||||
|
- Keep `CrawlerDetect` package
|
||||||
|
- Log bot patterns for analysis
|
||||||
|
- Consider adding honeypot endpoints
|
||||||
|
|
||||||
|
### 10.2 Data Validation
|
||||||
|
|
||||||
|
**Strict input validation:**
|
||||||
|
- Validate all tracking payload fields
|
||||||
|
- Sanitize URLs, titles, event names
|
||||||
|
- Limit string lengths (prevent database bloat)
|
||||||
|
- Validate JSON structure for event_properties
|
||||||
|
|
||||||
|
### 10.3 API Security
|
||||||
|
|
||||||
|
**Sync endpoint protection:**
|
||||||
|
- Use strong API keys (32+ characters)
|
||||||
|
- Rotate keys periodically
|
||||||
|
- IP whitelist for sync endpoints
|
||||||
|
- Use HTTPS only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11: Testing Strategy
|
||||||
|
|
||||||
|
### 11.1 Unit Tests
|
||||||
|
|
||||||
|
**Test coverage for:**
|
||||||
|
- `CollectController` validation logic
|
||||||
|
- `ProcessTrackingPayload` job
|
||||||
|
- `GoalEvaluationService`
|
||||||
|
- Bot detection
|
||||||
|
- Domain validation
|
||||||
|
|
||||||
|
### 11.2 Integration Tests
|
||||||
|
|
||||||
|
**Test scenarios:**
|
||||||
|
- End-to-end pageview tracking
|
||||||
|
- Event tracking and goal conversion
|
||||||
|
- Session management
|
||||||
|
- Website sync from main app
|
||||||
|
- Goal sync from main app
|
||||||
|
|
||||||
|
### 11.3 Load Testing
|
||||||
|
|
||||||
|
**Use tools:**
|
||||||
|
- Apache Bench (ab)
|
||||||
|
- wrk
|
||||||
|
- k6
|
||||||
|
|
||||||
|
**Test scenarios:**
|
||||||
|
```bash
|
||||||
|
# Simulate 1000 req/sec for 60 seconds
|
||||||
|
k6 run --vus 100 --duration 60s load-test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Load test script example:**
|
||||||
|
```javascript
|
||||||
|
// load-test.js
|
||||||
|
import http from 'k6/http';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
tid: 'TA-TEST123456',
|
||||||
|
vid: `visitor-${__VU}-${__ITER}`,
|
||||||
|
sid: `session-${__VU}`,
|
||||||
|
type: 'pageview',
|
||||||
|
url: 'https://example.com/test',
|
||||||
|
path: '/test',
|
||||||
|
});
|
||||||
|
|
||||||
|
http.post('http://collector.local/api/e', payload, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 12: Rollback Plan
|
||||||
|
|
||||||
|
### 12.1 Rollback Triggers
|
||||||
|
|
||||||
|
Rollback if:
|
||||||
|
- Error rate > 5%
|
||||||
|
- Data loss detected (missing tracking data)
|
||||||
|
- Performance degradation (p95 > 500ms)
|
||||||
|
- Queue backup (depth > 50,000)
|
||||||
|
|
||||||
|
### 12.2 Rollback Steps
|
||||||
|
|
||||||
|
1. **Immediate:**
|
||||||
|
- Update tracking script to point back to main app
|
||||||
|
- Disable collector app routing
|
||||||
|
|
||||||
|
2. **Within 1 hour:**
|
||||||
|
- Restore collection endpoints in main app
|
||||||
|
- Sync any data collected in collector to main app
|
||||||
|
|
||||||
|
3. **Post-incident:**
|
||||||
|
- Analyze root cause
|
||||||
|
- Fix issues
|
||||||
|
- Re-plan rollout
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 13: Cost Analysis
|
||||||
|
|
||||||
|
### 13.1 Infrastructure Costs
|
||||||
|
|
||||||
|
**Collector App (estimated monthly costs with shared database):**
|
||||||
|
- Load balancer: $20/month
|
||||||
|
- App instances (2x): $50/month each = $100/month
|
||||||
|
- ~~Database: $0 (using main app's database)~~
|
||||||
|
- Redis: $30/month
|
||||||
|
- Queue workers (2x): $30/month each = $60/month
|
||||||
|
- **Total: ~$210/month**
|
||||||
|
|
||||||
|
**Savings from reduced main app load:**
|
||||||
|
- Potential downgrade of main app instance: -$30/month
|
||||||
|
|
||||||
|
**Net increase: ~$180/month**
|
||||||
|
|
||||||
|
**Database costs:**
|
||||||
|
- No additional database server needed
|
||||||
|
- May need to upgrade main database instance slightly (+$10-20/month) to handle additional connections from collector app
|
||||||
|
- **Actual net increase: ~$190-200/month**
|
||||||
|
|
||||||
|
### 13.2 ROI Analysis
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- **Reliability:** Fewer dashboard outages during traffic spikes
|
||||||
|
- **Scalability:** Handle 10x traffic without main app changes
|
||||||
|
- **Development velocity:** Deploy collector changes independently
|
||||||
|
- **Security:** Better isolation of public endpoints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix A: Configuration Files
|
||||||
|
|
||||||
|
### Main App `.env` (no changes needed)
|
||||||
|
|
||||||
|
Since both apps share the same database, **no configuration changes** are needed in the main app's `.env` file.
|
||||||
|
|
||||||
|
The main app continues to work exactly as before.
|
||||||
|
|
||||||
|
### Collector App `.env`
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_NAME="Analytics Collector"
|
||||||
|
APP_ENV=production
|
||||||
|
APP_DEBUG=false
|
||||||
|
|
||||||
|
# SHARED DATABASE - Same as main app
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=main-db.local # Same database server as main app
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=analytics # Same database name as main app
|
||||||
|
DB_USERNAME=analytics_user
|
||||||
|
DB_PASSWORD=same_password_as_main_app
|
||||||
|
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
REDIS_HOST=collector-redis.local
|
||||||
|
|
||||||
|
COLLECTOR_RATE_LIMIT=120
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix B: nginx Configuration
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name track.yourdomain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name track.yourdomain.com;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/cert.pem;
|
||||||
|
ssl_certificate_key /path/to/key.pem;
|
||||||
|
|
||||||
|
root /var/www/analytics-collector/public;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req_zone $binary_remote_addr zone=collector:10m rate=120r/m;
|
||||||
|
limit_req zone=collector burst=20 nodelay;
|
||||||
|
|
||||||
|
# Static assets (tracking script)
|
||||||
|
location ~* \.(js|gif)$ {
|
||||||
|
expires 1h;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix C: Supervisor Configuration (Queue Workers)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[program:analytics-collector-worker]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /var/www/analytics-collector/artisan queue:work redis --queue=tracking --sleep=1 --tries=3 --max-jobs=1000 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=4
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/var/www/analytics-collector/storage/logs/worker.log
|
||||||
|
stopwaitsecs=3600
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Timeline Estimate
|
||||||
|
|
||||||
|
| Phase | Duration | Dependencies | Notes |
|
||||||
|
|-------|----------|--------------|-------|
|
||||||
|
| 1. Setup new app | 1 day | - | Laravel install + dependencies |
|
||||||
|
| 2. Database setup | 0.5 days | Phase 1 | Just configure `.env` - no migrations! |
|
||||||
|
| 3. Code extraction | 2-3 days | Phase 2 | Copy controllers, models, jobs, services |
|
||||||
|
| 4. Routes configuration | 1 day | Phase 3 | Set up API routes |
|
||||||
|
| 5. ~~Sync mechanism~~ | ~~Skipped~~ | - | **Not needed with shared DB!** |
|
||||||
|
| 6. ~~Data flow~~ | ~~Skipped~~ | - | **Automatic with shared DB!** |
|
||||||
|
| 7. Deployment prep | 2 days | Phase 4 | Infrastructure setup |
|
||||||
|
| 8. Monitoring setup | 1 day | Phase 7 | Metrics & alerting |
|
||||||
|
| 9. Optimizations | 2 days | Phase 7 | Caching, queue tuning |
|
||||||
|
| 10. Security review | 1 day | All above | CORS, rate limiting, validation |
|
||||||
|
| 11. Testing | 2-3 days | Phase 10 | Unit + integration + load tests |
|
||||||
|
| 12. Staged rollout | 1 week | Phase 11 | Gradual traffic migration |
|
||||||
|
|
||||||
|
**Total estimated time: 2-3 weeks** (reduced from 3-4 weeks due to no sync mechanism needed!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### Post-Launch (30 days)
|
||||||
|
|
||||||
|
- **Uptime:** > 99.9% for collector app
|
||||||
|
- **Response time:** p95 < 100ms for collection endpoints
|
||||||
|
- **Error rate:** < 0.5%
|
||||||
|
- **Data accuracy:** 100% match vs. pre-migration baseline
|
||||||
|
- **Cost:** Within 10% of projections
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Extracting the data collection endpoints into a separate collector app provides significant benefits in terms of scalability, reliability, and maintainability. With the **shared database approach**, implementation is significantly simplified:
|
||||||
|
|
||||||
|
### Key Benefits of Shared Database Architecture
|
||||||
|
|
||||||
|
1. **Simplicity:** No sync mechanism needed - data is immediately available to both apps
|
||||||
|
2. **Real-time:** Zero latency between data collection and analytics availability
|
||||||
|
3. **Reliability:** No sync failures or delays to handle
|
||||||
|
4. **Cost-effective:** Save ~$50/month vs. separate database approach
|
||||||
|
5. **Faster implementation:** 2-3 weeks instead of 3-4 weeks
|
||||||
|
|
||||||
|
### Implementation Summary
|
||||||
|
|
||||||
|
1. **Database strategy:** ✅ Shared database (both apps connect to same DB)
|
||||||
|
2. **Sync mechanism:** ✅ Not needed - data is instantly shared
|
||||||
|
3. **Deployment:** Gradual rollout with feature flags
|
||||||
|
4. **Monitoring:** Comprehensive metrics and alerting from day 1
|
||||||
|
|
||||||
|
### Trade-offs to Consider
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Simplest architecture
|
||||||
|
- No data sync complexity
|
||||||
|
- Real-time data availability
|
||||||
|
- Lower infrastructure costs
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Database is shared point of failure (mitigate with replication)
|
||||||
|
- Both apps depend on same database uptime
|
||||||
|
- Database connection pool must accommodate both apps
|
||||||
|
- Less flexibility for future independent scaling of databases
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
The **shared database approach is recommended** for your use case because:
|
||||||
|
- Your traffic volume doesn't require separate databases yet
|
||||||
|
- The simplicity significantly reduces complexity and development time
|
||||||
|
- You can always migrate to separate databases later if needed
|
||||||
|
- The cost savings (~$50/month) and time savings (1-2 weeks) are significant
|
||||||
|
|
||||||
|
This plan provides a clear roadmap for the extraction while minimizing risk and maintaining data integrity throughout the migration process.
|
||||||
Reference in New Issue
Block a user