# 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.