14 KiB
Demo Architecture - Production Demo System
Overview
This document describes the complete demo architecture for providing prospects with isolated, ephemeral demo sessions to explore the Bakery IA platform.
Key Features
- ✅ Session Isolation: Each prospect gets their own isolated copy of demo data
- ✅ Spanish Content: All demo data in Spanish for the Spanish market
- ✅ Two Business Models: Individual bakery and central baker satellite
- ✅ Automatic Cleanup: Sessions automatically expire after 30 minutes
- ✅ Read-Mostly Access: Prospects can explore but critical operations are restricted
- ✅ Production Ready: Scalable to 200+ concurrent demo sessions
Architecture Components
1. Demo Session Service
Location: services/demo_session/
Responsibilities:
- Create isolated demo sessions
- Manage session lifecycle (create, extend, destroy)
- Clone base demo data to virtual tenants
- Track session metrics and activity
Key Endpoints:
GET /api/demo/accounts # Get public demo account info
POST /api/demo/session/create # Create new demo session
POST /api/demo/session/extend # Extend session expiration
POST /api/demo/session/destroy # Destroy session
GET /api/demo/session/{id} # Get session info
GET /api/demo/stats # Get usage statistics
2. Demo Data Seeding
Location: scripts/demo/
Scripts:
seed_demo_users.py- Creates demo user accountsseed_demo_tenants.py- Creates base demo tenants (templates)seed_demo_inventory.py- Populates inventory with Spanish data (25 ingredients per template)clone_demo_tenant.py- Clones data from base template to virtual tenant (runs as K8s Job)
Demo Accounts:
Individual Bakery (Panadería San Pablo)
Email: demo.individual@panaderiasanpablo.com
Password: DemoSanPablo2024!
Business Model: Producción Local
Features: Production, Recipes, Inventory, Forecasting, POS, Sales
Central Baker Satellite (Panadería La Espiga)
Email: demo.central@panaderialaespiga.com
Password: DemoLaEspiga2024!
Business Model: Obrador Central + Punto de Venta
Features: Suppliers, Inventory, Orders, POS, Sales, Forecasting
3. Gateway Middleware
Location: gateway/app/middleware/demo_middleware.py
Responsibilities:
- Intercept requests with demo session IDs
- Inject virtual tenant ID
- Enforce operation restrictions
- Track session activity
Allowed Operations:
# Read - All allowed
GET, HEAD, OPTIONS: *
# Limited Write - Realistic testing
POST: /api/pos/sales, /api/orders, /api/inventory/adjustments
PUT: /api/pos/sales/*, /api/orders/*
# Blocked
DELETE: All (read-only for destructive operations)
4. Redis Cache Layer
Purpose: Store frequently accessed demo session data
Data Cached:
- Session metadata
- Inventory summaries
- POS session data
- Recent sales
TTL: 30 minutes (auto-cleanup)
5. Kubernetes Resources
Databases:
demo-session-db- Tracks session records
Services:
demo-session-service- Main demo service (2 replicas)
Jobs (Initialization):
demo-seed-users- Creates demo usersdemo-seed-tenants- Creates demo tenant templatesdemo-seed-inventory- Populates inventory data (25 ingredients per tenant)
Dynamic Jobs (Runtime):
demo-clone-{virtual_tenant_id}- Created per session to clone data from template
CronJob (Maintenance):
demo-session-cleanup- Runs hourly to cleanup expired sessions
RBAC:
demo-session-sa- ServiceAccount for demo-session-servicedemo-session-job-creator- Role allowing job creation and pod managementdemo-seed-role- Role for seed jobs to access databases
Data Flow
Session Creation
1. User clicks "Probar Demo" on website
↓
2. Frontend calls POST /api/demo/session/create
{
"demo_account_type": "individual_bakery"
}
↓
3. Demo Session Service:
- Generates unique session_id: "demo_abc123..."
- Creates virtual_tenant_id: UUID
- Stores session in database
- Returns session_token (JWT)
↓
4. Kubernetes Job Cloning (background):
- Demo service triggers K8s Job with clone script
- Job container uses CLONE_JOB_IMAGE (inventory-service image)
- Clones inventory data from base template tenant
- Uses ORM models for safe data copying
- Job runs with IfNotPresent pull policy (works in dev & prod)
↓
5. Frontend receives:
{
"session_id": "demo_abc123...",
"virtual_tenant_id": "uuid-here",
"expires_at": "2025-10-02T12:30:00Z",
"session_token": "eyJ..."
}
↓
6. Frontend stores session_token in cookie/localStorage
All subsequent requests include:
Header: X-Demo-Session-Id: demo_abc123...
Request Handling
1. Request arrives at Gateway
↓
2. Demo Middleware checks:
- Is X-Demo-Session-Id present?
- Is session still active?
- Is operation allowed?
↓
3. If valid:
- Injects X-Tenant-Id: {virtual_tenant_id}
- Routes to appropriate service
↓
4. Service processes request:
- Reads/writes data for virtual tenant
- No knowledge of demo vs. real tenant
↓
5. Response returned to user
Session Cleanup
Every hour (CronJob):
1. Demo Cleanup Service queries:
SELECT * FROM demo_sessions
WHERE status = 'active'
AND expires_at < NOW()
↓
2. For each expired session:
- Mark as 'expired'
- Delete all virtual tenant data
- Delete Redis keys
- Update statistics
↓
3. Weekly cleanup:
DELETE FROM demo_sessions
WHERE status = 'destroyed'
AND destroyed_at < NOW() - INTERVAL '7 days'
Database Schema
demo_sessions Table
CREATE TABLE demo_sessions (
id UUID PRIMARY KEY,
session_id VARCHAR(100) UNIQUE NOT NULL,
-- Ownership
user_id UUID,
ip_address VARCHAR(45),
user_agent VARCHAR(500),
-- Demo linking
base_demo_tenant_id UUID NOT NULL,
virtual_tenant_id UUID NOT NULL,
demo_account_type VARCHAR(50) NOT NULL,
-- Lifecycle
status VARCHAR(20) NOT NULL, -- active, expired, destroyed
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
last_activity_at TIMESTAMP WITH TIME ZONE,
destroyed_at TIMESTAMP WITH TIME ZONE,
-- Metrics
request_count INTEGER DEFAULT 0,
data_cloned BOOLEAN DEFAULT FALSE,
redis_populated BOOLEAN DEFAULT FALSE,
-- Metadata
metadata JSONB
);
CREATE INDEX idx_session_id ON demo_sessions(session_id);
CREATE INDEX idx_virtual_tenant ON demo_sessions(virtual_tenant_id);
CREATE INDEX idx_status ON demo_sessions(status);
CREATE INDEX idx_expires_at ON demo_sessions(expires_at);
tenants Table (Updated)
ALTER TABLE tenants ADD COLUMN is_demo BOOLEAN DEFAULT FALSE;
ALTER TABLE tenants ADD COLUMN is_demo_template BOOLEAN DEFAULT FALSE;
ALTER TABLE tenants ADD COLUMN base_demo_tenant_id UUID;
ALTER TABLE tenants ADD COLUMN demo_session_id VARCHAR(100);
ALTER TABLE tenants ADD COLUMN demo_expires_at TIMESTAMP WITH TIME ZONE;
CREATE INDEX idx_is_demo ON tenants(is_demo);
CREATE INDEX idx_demo_session ON tenants(demo_session_id);
Deployment
Initial Deployment
# 1. Deploy infrastructure (databases, redis, rabbitmq)
kubectl apply -k infrastructure/kubernetes/overlays/prod
# 2. Run migrations
# (Automatically handled by migration jobs)
# 3. Seed demo data
# (Automatically handled by demo-seed-* jobs)
# 4. Verify demo system
kubectl get jobs -n bakery-ia | grep demo-seed
kubectl logs -f job/demo-seed-users -n bakery-ia
kubectl logs -f job/demo-seed-tenants -n bakery-ia
kubectl logs -f job/demo-seed-inventory -n bakery-ia
# 5. Test demo session creation
curl -X POST http://your-domain/api/demo/session/create \
-H "Content-Type: application/json" \
-d '{"demo_account_type": "individual_bakery"}'
Using Tilt (Local Development)
# Start Tilt
tilt up
# Demo resources in Tilt UI:
# - databases: demo-session-db
# - migrations: demo-session-migration
# - services: demo-session-service
# - demo-init: demo-seed-users, demo-seed-tenants, demo-seed-inventory
# - config: patch-demo-session-env (sets CLONE_JOB_IMAGE dynamically)
# Tilt automatically:
# 1. Gets inventory-service image tag (e.g., tilt-abc123)
# 2. Patches demo-session-service with CLONE_JOB_IMAGE env var
# 3. Clone jobs use this image with IfNotPresent pull policy
Monitoring
Key Metrics
# Session Statistics
GET /api/demo/stats
{
"total_sessions": 1250,
"active_sessions": 45,
"expired_sessions": 980,
"destroyed_sessions": 225,
"avg_duration_minutes": 18.5,
"total_requests": 125000
}
Health Checks
# Demo Session Service
curl http://demo-session-service:8000/health
# Check active sessions
kubectl exec -it deployment/demo-session-service -- \
python -c "from app.services import *; print(get_active_sessions())"
Logs
# Demo session service logs
kubectl logs -f deployment/demo-session-service -n bakery-ia
# Demo seed job logs
kubectl logs job/demo-seed-inventory -n bakery-ia
# Cleanup cron job logs
kubectl logs -l app=demo-cleanup -n bakery-ia --tail=100
Scaling Considerations
Current Limits
- Concurrent Sessions: ~200 (2 replicas × ~100 sessions each)
- Redis Memory: ~1-2 GB (10 MB per session × 200)
- PostgreSQL: ~5-10 GB (30 MB per virtual tenant × 200)
- Session Duration: 30 minutes (configurable)
- Extensions: Maximum 3 per session
Scaling Up
# Scale demo-session-service
kubectl scale deployment/demo-session-service --replicas=4 -n bakery-ia
# Increase Redis memory (if needed)
# Edit redis deployment, increase memory limits
# Adjust session settings
# Edit demo-session configmap:
DEMO_SESSION_DURATION_MINUTES: 45 # Increase session time
DEMO_SESSION_MAX_EXTENSIONS: 5 # Allow more extensions
Security
Public Demo Credentials
Demo credentials are intentionally public for prospect access:
- Published on marketing website
- Included in demo documentation
- Safe because sessions are isolated and ephemeral
Restrictions
- No Destructive Operations: DELETE blocked
- Limited Modifications: Only realistic testing operations
- No Sensitive Data Access: Cannot change passwords, billing, etc.
- Automatic Expiration: Sessions auto-destroy after 30 minutes
- Rate Limiting: Standard gateway rate limits apply
- No AI Training: Forecast API blocked for demo accounts (no trained models)
- Scheduler Prevention: Procurement scheduler filters out demo tenants
Data Privacy
- No real customer data in demo tenants
- Session data automatically deleted
- Anonymized analytics only
Troubleshooting
Session Creation Fails
# Check demo-session-service health
kubectl get pods -l app=demo-session-service -n bakery-ia
# Check logs
kubectl logs deployment/demo-session-service -n bakery-ia --tail=50
# Verify base demo tenants exist
kubectl exec -it deployment/tenant-service -- \
psql $TENANT_DATABASE_URL -c \
"SELECT id, name, subdomain FROM tenants WHERE is_demo_template = true;"
Sessions Not Cleaning Up
# Check cleanup cronjob
kubectl get cronjobs -n bakery-ia
kubectl get jobs -l app=demo-cleanup -n bakery-ia
# Manually trigger cleanup
kubectl create job --from=cronjob/demo-session-cleanup manual-cleanup-$(date +%s) -n bakery-ia
# Check for orphaned sessions
kubectl exec -it deployment/demo-session-service -- \
psql $DEMO_SESSION_DATABASE_URL -c \
"SELECT status, COUNT(*) FROM demo_sessions GROUP BY status;"
Redis Connection Issues
# Test Redis connectivity
kubectl exec -it deployment/demo-session-service -- \
python -c "import redis; r=redis.Redis(host='redis-service'); print(r.ping())"
# Check Redis memory usage
kubectl exec -it deployment/redis -- redis-cli INFO memory
Technical Implementation Details
Data Cloning Architecture
Choice: Kubernetes Job-based Cloning (selected over service-based endpoints)
Why K8s Jobs:
- Database-level operations (faster than API calls)
- Scalable (one job per session, isolated execution)
- No service-specific clone endpoints needed
- Works in both dev (Tilt) and production
How it Works:
- Demo-session-service creates K8s Job via K8s API
- Job uses
CLONE_JOB_IMAGEenvironment variable (configured image) - In Dev (Tilt):
patch-demo-session-envsets dynamic Tilt image tag - In Production: Deployment manifest has stable release tag
- Job runs
clone_demo_tenant.pywithimagePullPolicy: IfNotPresent - Script uses ORM models to clone data safely
Environment-based Image Configuration:
# Demo-session deployment
env:
- name: CLONE_JOB_IMAGE
value: "bakery/inventory-service:latest" # Overridden by Tilt in dev
# Tilt automatically patches this to match actual inventory-service tag
# e.g., bakery/inventory-service:tilt-abc123
AI Model Restrictions
Fake Models in Database:
- Demo tenants have AI model records in database
- No actual model files (.pkl, .h5) stored
- Forecast API blocked at gateway level for demo accounts
- Returns user-friendly error message
Scheduler Prevention:
- Procurement scheduler filters
is_demo = truetenants - Prevents automated procurement runs on demo data
- Manual procurement still allowed for realistic testing
Future Enhancements
- Analytics Dashboard: Track demo → paid conversion rates
- Guided Tours: In-app tutorials for demo users
- Custom Demo Scenarios: Let prospects choose specific features
- Demo Recordings: Capture anonymized session recordings
- Multi-Region: Deploy demo infrastructure in EU, US, LATAM
- Sales & Orders Cloning: Extend clone script to copy sales and orders data