Analytics Warehouse Operations Guide¶
Note: The ETL pipeline is now an independent microservice located at
src/pipelines/analytics_etl/.
See the ETL Pipeline README for architecture and development details.
Overview¶
The analytics warehouse provides 10-100x faster query performance through pre-aggregated data stored in MongoDB dimensional tables.
Key Benefits: - 8-10x query speedup (confirmed: 8.8x in production testing) - Dimensional modeling for flexible analytics - Incremental ETL with checkpoint tracking - Deduplication and data quality controls - Backward compatible with existing APIs
Architecture¶
Database Structure¶
Warehouse Database: {prefix}_analytics (e.g., craigh_analytics)
MongoDB Logs (event='<prefix>_api')
↓
[Extract] - Only API events, checkpoint tracking
↓
[Transform] - Validation, cleaning, deduplication
↓
[Load] - Upsert dimensions, insert facts
↓
Analytics Warehouse (fact + dimension tables)
↓
[Aggregate] - Hourly/daily summaries
↓
Fast API Queries (30-50ms vs 300-500ms)
Collections¶
Fact Tables:
- fact_interaction - Atomic grain (1 row per log)
- Embedded dimension objects
- Time dimensions (ts, date, year, month, week, day, hour)
- Measures (response_time_ms, token_counts, cost_usd)
- fact_hourly_summary - Pre-aggregated hourly metrics
- fact_daily_summary - Pre-aggregated daily metrics
Dimension Tables:
- dim_cat - Catalog/CAT information
- dim_topic - Topics/categories
- dim_model - AI models (gpt-4o, claude-3, etc.)
- dim_persona - User personas
- dim_question - Unique questions (deduplicated)
- dim_answer - Unique answers (deduplicated)
- dim_session - User sessions
- dim_user - User information
Metadata Tables:
- etl_log - ETL job execution history
- etl_checkpoint - Last processed timestamp per CAT
Daily Operations¶
1. Run Incremental ETL¶
Extract new logs since last checkpoint:
What it does:
- Checks checkpoint for last processed timestamp
- Extracts new logs where event='craigh_api' and ts > checkpoint
- Transforms and validates data
- Loads into warehouse (upserts dimensions, inserts facts)
- Updates checkpoint for next run
- Logs execution to etl_log collection
Expected output:
======================================================================
ETL PIPELINE - CRAIGH
======================================================================
Running INCREMENTAL ETL (processing new logs only)...
✓ Extracted 45 logs in 0.32s
✓ Transformed 45 logs in 0.15s
✓ Loaded 45 logs, skipped 0 duplicates, 0 errors in 1.23s
✓ Updated checkpoint to: 1737399845.123
======================================================================
Extracted: 45
Transformed: 45
Loaded: 45
Skipped: 0 (duplicates)
Errors: 0
======================================================================
2. Run Aggregations¶
Update summary tables:
What it does:
- Queries fact_interaction for new/updated data
- Aggregates by hour and day
- Upserts into fact_hourly_summary and fact_daily_summary
- Creates indexes for fast queries
Expected output:
================================================================================
ANALYTICS AGGREGATION: craigh
================================================================================
✓ Initialized aggregator for cat 'craigh'
Running aggregations...
Hourly Summary:
Processed: 24 hours
Created: 18 new records
Updated: 6 existing records
Daily Summary:
Processed: 7 days
Created: 5 new records
Updated: 2 existing records
⏱️ Total duration: 2.34s
================================================================================
3. Check Warehouse Status¶
Verify data is loaded correctly:
Or inspect manually:
Scheduling (Production)¶
Cron Setup¶
Recommended schedule:
# Edit crontab
crontab -e
# Run incremental ETL every hour (at minute 0)
0 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh >> /var/log/bast-etl-craigh.log 2>&1
# Run aggregations 5 minutes after ETL (at minute 5)
5 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_aggregation -- craigh >> /var/log/bast-agg-craigh.log 2>&1
For multiple CATs:
# Stagger runs to avoid resource contention
0 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh >> /var/log/bast-etl-craigh.log 2>&1
5 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_aggregation -- craigh >> /var/log/bast-agg-craigh.log 2>&1
10 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_etl -- advisoruat >> /var/log/bast-etl-advisor.log 2>&1
15 * * * * cd /path/to/bast-ai && pants run src/pipelines/analytics_etl/scripts:run_aggregation -- advisoruat >> /var/log/bast-agg-advisor.log 2>&1
Systemd Timer (Alternative)¶
# Create timer file
sudo nano /etc/systemd/system/bast-etl-craigh.timer
[Unit]
Description=Bast ETL for craigh (hourly)
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
Full Rebuild (Rarely Needed)¶
When to Use¶
- Initial warehouse setup
- Data corruption recovery
- Schema changes requiring reload
- Major data quality issues
How to Run¶
# Step 1: Backup current warehouse (optional but recommended)
mongodump --db craigh_analytics --out /backup/craigh_analytics_$(date +%Y%m%d)
# Step 2: Clear warehouse (optional - deduplication handles this)
# mongo craigh_analytics --eval "db.dropDatabase()"
# Step 3: Run full ETL (processes all logs, skips duplicates)
pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh --full
# Step 4: Run aggregations
pants run src/pipelines/analytics_etl/scripts:run_aggregation -- craigh
# Step 5: Verify
pants run src/apis/analytics/scripts:inspect_warehouse_data -- craigh
Testing & Validation¶
Compare Endpoints¶
Test performance and data accuracy:
# Terminal 1: Start API server
pants run src/apis/analytics/integration:api_tester -- craigh
# Terminal 2: Run comparison (wait for server to start)
PANTS_CONCURRENT=True pants run src/pipelines/analytics_etl/scripts:compare_endpoints -- craigh
Expected results:
Performance Comparison:
Legacy: 415.04ms
Warehouse: 47.17ms
Speedup: 8.80x faster
Data Comparison:
Total Interactions: ✓ Match (1,143 = 1,143)
Topic Counts: ✓ Match (29 = 29)
Model Counts: ✓ Match (11 = 11)
Persona Counts: ✓ Match (6 = 6)
Questions: ✓ Match (705 = 705)
Answers: ✓ Match (1,093 = 1,093)
Manual API Testing¶
# High-performance warehouse endpoint
curl "http://localhost:9090/api/v1/analytics/all_warehouse?catname=craigh" | jq .
# Original endpoint (for comparison)
curl "http://localhost:9090/api/v1/analytics/all?catname=craigh" | jq .
# Test with filters
curl "http://localhost:9090/api/v1/analytics/all_warehouse?catname=craigh&startDate=2026-01-01&endDate=2026-12-31&model=gpt-4o" | jq .
Monitoring¶
Check ETL Status¶
MongoDB queries:
// Recent ETL jobs
use craigh_analytics
db.etl_log.find().sort({job_start_time: -1}).limit(10).pretty()
// Failed jobs
db.etl_log.find({status: "failed"}).pretty()
// Current checkpoint
db.etl_checkpoint.find({cat_name: "craigh"}).pretty()
Check Data Counts¶
// Fact interaction count
db.fact_interaction.count()
// Recent interactions (last 24 hours)
db.fact_interaction.count({
ts: {$gte: (Date.now() / 1000) - 86400}
})
// Dimension counts
db.dim_topic.count()
db.dim_model.count()
db.dim_user.count()
// Summary table counts
db.fact_hourly_summary.count()
db.fact_daily_summary.count()
Performance Monitoring¶
Check API response times:
# Monitor in real-time
while true; do
curl -s -w "\nTime: %{time_total}s\n" \
"http://localhost:9090/api/v1/analytics/all_warehouse?catname=craigh" \
| jq '.performance_ms'
sleep 5
done
Expected values: - Warehouse: 30-50ms (fast) - Legacy: 300-500ms (slow)
Troubleshooting¶
ETL Job Failed¶
Symptoms: - Cron job exits with error - No new data in warehouse - ETL log shows failed status
Diagnosis:
# Check ETL logs
tail -f /var/log/bast-etl-craigh.log
# Check MongoDB ETL log
mongo craigh_analytics --eval "db.etl_log.find({status: 'failed'}).sort({job_start_time: -1}).limit(1).pretty()"
Common causes: 1. MongoDB connection issues 2. Invalid data in source logs 3. Disk space full 4. Permission issues
Fix:
# 1. Verify MongoDB connection
mongo --eval "db.runCommand({ping: 1})"
# 2. Check disk space
df -h
# 3. Re-run manually with verbose logging
pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh
Missing Data¶
Symptoms: - Dashboard shows no recent data - API returns old data - Warehouse counts don't match expectations
Diagnosis:
// Check checkpoint
db.etl_checkpoint.find({cat_name: "craigh"})
// Check latest fact
db.fact_interaction.find().sort({ts: -1}).limit(1).pretty()
// Check ETL log
db.etl_log.find().sort({job_start_time: -1}).limit(5).pretty()
Fix:
# Force re-run (updates checkpoint)
pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh
# If still missing, run full ETL
pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh --full
Data Accuracy Issues¶
Symptoms: - Counts don't match between legacy and warehouse - Missing topics/models/personas - Incorrect aggregations
Diagnosis:
# Compare endpoints
PANTS_CONCURRENT=True pants run src/pipelines/analytics_etl/scripts:compare_endpoints -- craigh
# Inspect warehouse data
pants run src/apis/analytics/scripts:inspect_warehouse_data -- craigh
Common causes:
1. Event filtering (only event='<prefix>_api' is processed)
2. Deduplication (duplicates are skipped)
3. Missing persona/topic labels in source data
4. Nested field access issues (should use model.name not model_name)
Fix:
# Re-run full ETL to rebuild warehouse
pants run src/pipelines/analytics_etl/scripts:run_etl -- craigh --full
pants run src/pipelines/analytics_etl/scripts:run_aggregation -- craigh
Slow Query Performance¶
Symptoms: - Warehouse queries take > 100ms - API timeouts - Dashboard slow to load
Diagnosis:
// Check if indexes exist
db.fact_interaction.getIndexes()
db.fact_hourly_summary.getIndexes()
// Run explain on slow query
db.fact_interaction.find({ts: {$gte: 1737000000}}).explain("executionStats")
Fix:
// Create missing indexes
db.fact_interaction.createIndex({ts: 1})
db.fact_interaction.createIndex({"cat._id": 1, ts: 1})
db.fact_interaction.createIndex({"model.name": 1})
db.fact_interaction.createIndex({"topic.label": 1})
// For aggregations
db.fact_hourly_summary.createIndex({hour_bucket: 1})
db.fact_daily_summary.createIndex({day: 1})
Best Practices¶
ETL Operations¶
- Run incremental ETL frequently (hourly recommended)
- Run aggregations after ETL (5-10 minute delay)
- Monitor ETL logs for failures
- Backup before major changes (schema updates, full rebuilds)
- Test in dev before production
Data Quality¶
- Validate source data before loading
- Check for duplicates (source_log_id uniqueness)
- Monitor null values (especially topics/personas)
- Verify event filtering (only API events processed)
- Compare with legacy endpoint after changes
Performance¶
- Create indexes on frequently queried fields
- Use aggregation summaries for dashboards
- Cache API responses (5-15 minute TTL)
- Monitor query times (alert if > 100ms)
- Partition large tables if needed (future)
Migration Strategy¶
Phase 1: Deploy Warehouse (Current)¶
- ✅ Deploy warehouse code
- ✅
/allendpoint unchanged (backward compatible) - ✅
/all_warehouseavailable for testing - ✅ Run ETL to populate warehouse
Phase 2: Validation (1-2 weeks)¶
- ✅ Run comparison tool regularly
- ✅ Monitor performance metrics
- ✅ Validate data accuracy
- ✅ Test with production traffic
Phase 3: Gradual Migration (TBD)¶
Option A: Update clients gradually
- Dashboard 1: Switch to /all_warehouse
- Dashboard 2: Switch to /all_warehouse
- Monitor for issues
- Eventually deprecate /all_legacy
Option B: Swap implementation
- Update /all to use warehouse logic
- Keep /all_legacy as fallback
- No client changes needed!
Phase 4: Cleanup (Future)¶
- Remove
/all_legacyendpoint - Archive old documentation
- Optimize warehouse schema
- Add new features (real-time streaming, etc.)
Support¶
Documentation: - Performance Comparison - API Endpoints Reference - ETL Design Document
Commands Reference:
# ETL
pants run src/pipelines/analytics_etl/scripts:run_etl -- <catname> [--full]
pants run src/pipelines/analytics_etl/scripts:run_aggregation -- <catname>
# Testing
pants run src/apis/analytics/integration:api_tester -- <catname>
pants run src/pipelines/analytics_etl/scripts:compare_endpoints -- <catname>
# Inspection
pants run src/pipelines/analytics_etl/scripts:check_warehouse_status -- <catname>
pants run src/pipelines/analytics_etl/scripts:inspect_warehouse_data -- <catname>
Questions? Check the troubleshooting section above or review the ETL logs.