Skip to content

Latest commit

 

History

History
1172 lines (973 loc) · 30.6 KB

File metadata and controls

1172 lines (973 loc) · 30.6 KB
layout default
title Chapter 5: Production Deployment
nav_order 5
has_children false
parent Flowise LLM Orchestration

Chapter 5: Production Deployment

Welcome to Chapter 5: Production Deployment. In this part of Flowise LLM Orchestration: Deep Dive Tutorial, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.

Scaling, monitoring, and maintaining Flowise applications in production

🎯 Learning Objectives

By the end of this chapter, you'll understand:

  • Deploying Flowise applications with Docker and Kubernetes
  • Scaling workflows for high-throughput scenarios
  • Implementing monitoring and observability
  • Setting up CI/CD pipelines for workflow deployment
  • Performance optimization and cost management

🐳 Docker Deployment

Production Docker Configuration

# Dockerfile for production Flowise deployment
FROM node:18-alpine AS base

# Install dependencies for native modules
RUN apk add --no-cache python3 make g++ sqlite-dev

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY yarn.lock ./

# Install dependencies
RUN yarn install --frozen-lockfile --production=false

# Copy source code
COPY . .

# Build application
RUN yarn build

# Production stage
FROM node:18-alpine AS production

# Install runtime dependencies
RUN apk add --no-cache sqlite python3

WORKDIR /app

# Copy built application
COPY --from=base /app/dist ./dist
COPY --from=base /app/package*.json ./
COPY --from=base /app/yarn.lock ./

# Install production dependencies only
RUN yarn install --frozen-lockfile --production=true

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S flowise -u 1001

# Create data directory
RUN mkdir -p /app/packages/server/data
RUN chown -R flowise:nodejs /app
USER flowise

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

# Start application
CMD ["yarn", "start"]

Docker Compose for Production

# docker-compose.prod.yml
version: '3.8'

services:
  flowise:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_PATH=/app/data/database.sqlite
      - APIKEY_PATH=/app/data
      - SECRETKEY_PATH=/app/data
      - LOG_PATH=/app/logs
      - BLOB_STORAGE_PATH=/app/storage
    volumes:
      - flowise_data:/app/data
      - flowise_logs:/app/logs
      - flowise_storage:/app/storage
    depends_on:
      - postgres
      - redis
    networks:
      - flowise_network
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=flowise
      - POSTGRES_USER=flowise
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - flowise_network
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    networks:
      - flowise_network
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/ssl/certs
      - nginx_logs:/var/log/nginx
    depends_on:
      - flowise
    networks:
      - flowise_network
    restart: unless-stopped

volumes:
  flowise_data:
  flowise_logs:
  flowise_storage:
  postgres_data:
  redis_data:
  nginx_logs:

networks:
  flowise_network:
    driver: bridge

Nginx Configuration

# nginx.conf for Flowise production
events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 100M;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;

    upstream flowise_backend {
        least_conn;
        server flowise:3000;
    }

    server {
        listen 80;
        server_name your-domain.com;

        # Redirect to HTTPS
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name your-domain.com;

        # SSL configuration
        ssl_certificate /etc/ssl/certs/fullchain.pem;
        ssl_certificate_key /etc/ssl/certs/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;

        # Security headers
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

        # API endpoints
        location /api/ {
            proxy_pass http://flowise_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
            proxy_read_timeout 86400;
        }

        # Web interface
        location / {
            proxy_pass http://flowise_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }

        # Static file caching
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

☸️ Kubernetes Deployment

Kubernetes Manifests

# flowise-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flowise
  namespace: flowise-production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flowise
  template:
    metadata:
      labels:
        app: flowise
    spec:
      containers:
      - name: flowise
        image: your-registry/flowise:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_TYPE
          value: "postgres"
        - name: DATABASE_HOST
          valueFrom:
            secretKeyRef:
              name: flowise-secrets
              key: db-host
        - name: DATABASE_USER
          valueFrom:
            secretKeyRef:
              name: flowise-secrets
              key: db-user
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: flowise-secrets
              key: db-password
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: flowise-secrets
              key: redis-url
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /api/v1/ping
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/v1/health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        volumeMounts:
        - name: flowise-data
          mountPath: /app/packages/server/data
      volumes:
      - name: flowise-data
        persistentVolumeClaim:
          claimName: flowise-data-pvc

---
apiVersion: v1
kind: Service
metadata:
  name: flowise-service
  namespace: flowise-production
spec:
  selector:
    app: flowise
  ports:
  - port: 3000
    targetPort: 3000
  type: ClusterIP

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: flowise-ingress
  namespace: flowise-production
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - your-flowise-domain.com
    secretName: flowise-tls
  rules:
  - host: your-flowise-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: flowise-service
            port:
              number: 3000

Horizontal Pod Autoscaling

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: flowise-hpa
  namespace: flowise-production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: flowise
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60
      - type: Pods
        value: 2
        periodSeconds: 60

📊 Monitoring and Observability

Prometheus Metrics

// metrics.js - Prometheus metrics for Flowise
const promClient = require('prom-client');

// Create registry
const register = new promClient.Registry();

// Add default metrics
promClient.collectDefaultMetrics({ register });

// Custom metrics
const httpRequestDuration = new promClient.Histogram({
  name: 'flowise_http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.5, 1, 2, 5, 10]
});

const workflowExecutionCount = new promClient.Counter({
  name: 'flowise_workflow_executions_total',
  help: 'Total number of workflow executions',
  labelNames: ['workflow_id', 'status']
});

const workflowExecutionDuration = new promClient.Histogram({
  name: 'flowise_workflow_execution_duration_seconds',
  help: 'Duration of workflow executions',
  labelNames: ['workflow_id'],
  buckets: [1, 5, 10, 30, 60, 120, 300]
});

const nodeExecutionCount = new promClient.Counter({
  name: 'flowise_node_executions_total',
  help: 'Total number of node executions',
  labelNames: ['node_type', 'status']
});

const activeConnections = new promClient.Gauge({
  name: 'flowise_active_connections',
  help: 'Number of active connections'
});

const memoryUsage = new promClient.Gauge({
  name: 'flowise_memory_usage_bytes',
  help: 'Memory usage in bytes'
});

const cpuUsage = new promClient.Gauge({
  name: 'flowise_cpu_usage_percentage',
  help: 'CPU usage percentage'
});

// Register metrics
register.registerMetric(httpRequestDuration);
register.registerMetric(workflowExecutionCount);
register.registerMetric(workflowExecutionDuration);
register.registerMetric(nodeExecutionCount);
register.registerMetric(activeConnections);
register.registerMetric(memoryUsage);
register.registerMetric(cpuUsage);

// Metrics middleware
function metricsMiddleware(req, res, next) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;

    httpRequestDuration
      .labels(req.method, req.route?.path || req.path, res.statusCode)
      .observe(duration);
  });

  next();
}

// Workflow monitoring
class WorkflowMonitor {
  constructor() {
    this.activeWorkflows = new Map();
  }

  startWorkflow(workflowId, executionId) {
    workflowExecutionCount.inc({ workflow_id: workflowId, status: 'started' });
    this.activeWorkflows.set(executionId, {
      workflowId,
      startTime: Date.now()
    });
  }

  completeWorkflow(executionId, success = true) {
    const workflow = this.activeWorkflows.get(executionId);
    if (!workflow) return;

    const duration = (Date.now() - workflow.startTime) / 1000;

    workflowExecutionDuration
      .labels(workflow.workflowId)
      .observe(duration);

    workflowExecutionCount.inc({
      workflow_id: workflow.workflowId,
      status: success ? 'completed' : 'failed'
    });

    this.activeWorkflows.delete(executionId);
  }

  recordNodeExecution(nodeType, success = true) {
    nodeExecutionCount.inc({
      node_type: nodeType,
      status: success ? 'success' : 'failure'
    });
  }
}

// System metrics collection
class SystemMetricsCollector {
  constructor() {
    this.collectInterval = setInterval(() => this.collect(), 5000);
  }

  async collect() {
    try {
      // Memory usage
      const memUsage = process.memoryUsage();
      memoryUsage.set(memUsage.heapUsed);

      // CPU usage (simplified)
      const cpuUsagePercent = await this.getCpuUsage();
      cpuUsage.set(cpuUsagePercent);

      // Active connections (if using WebSocket)
      // This would depend on your WebSocket implementation
      // activeConnections.set(this.getActiveConnections());

    } catch (error) {
      console.error('Error collecting system metrics:', error);
    }
  }

  async getCpuUsage() {
    // Simplified CPU usage calculation
    const startUsage = process.cpuUsage();
    await new Promise(resolve => setTimeout(resolve, 100));
    const endUsage = process.cpuUsage(startUsage);

    const totalUsage = endUsage.user + endUsage.system;
    const totalTime = Date.now() - this.lastCpuCheck;

    this.lastCpuCheck = Date.now();

    return (totalUsage / 1000) / (totalTime / 1000) * 100;
  }

  destroy() {
    if (this.collectInterval) {
      clearInterval(this.collectInterval);
    }
  }
}

module.exports = {
  register,
  metricsMiddleware,
  WorkflowMonitor,
  SystemMetricsCollector
};

Grafana Dashboards

// grafana-dashboard.json
{
  "dashboard": {
    "title": "Flowise Production Monitoring",
    "tags": ["flowise", "production"],
    "timezone": "browser",
    "panels": [
      {
        "title": "HTTP Request Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(flowise_http_request_duration_seconds_count[5m])",
            "legendFormat": "{{method}} {{route}}"
          }
        ]
      },
      {
        "title": "Workflow Execution Duration",
        "type": "heatmap",
        "targets": [
          {
            "expr": "flowise_workflow_execution_duration_seconds",
            "legendFormat": "{{workflow_id}}"
          }
        ]
      },
      {
        "title": "Node Execution Success Rate",
        "type": "bargauge",
        "targets": [
          {
            "expr": "rate(flowise_node_executions_total{status='success'}[5m]) / rate(flowise_node_executions_total[5m]) * 100",
            "legendFormat": "{{node_type}}"
          }
        ]
      },
      {
        "title": "System Resources",
        "type": "graph",
        "targets": [
          {
            "expr": "flowise_memory_usage_bytes / 1024 / 1024",
            "legendFormat": "Memory Usage (MB)"
          },
          {
            "expr": "flowise_cpu_usage_percentage",
            "legendFormat": "CPU Usage (%)"
          }
        ]
      }
    ]
  }
}

🔄 CI/CD Pipeline

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy Flowise to Production

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Run linting
        run: yarn lint

      - name: Run tests
        run: yarn test --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Build application
        run: yarn build

      - name: Build Docker image
        run: |
          docker build -t flowise:${{ github.sha }} .
          docker tag flowise:${{ github.sha }} flowise:latest

      - name: Push to registry
        run: |
          echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
          docker tag flowise:${{ github.sha }} your-registry/flowise:${{ github.sha }}
          docker push your-registry/flowise:${{ github.sha }}

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          kubectl set image deployment/flowise flowise=your-registry/flowise:${{ github.sha }} --namespace=flowise-staging
          kubectl rollout status deployment/flowise --namespace=flowise-staging

      - name: Run integration tests
        run: |
          # Run tests against staging environment
          npm run test:e2e -- --url=https://staging.your-domain.com

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to production
        run: |
          kubectl set image deployment/flowise flowise=your-registry/flowise:${{ github.sha }} --namespace=flowise-production
          kubectl rollout status deployment/flowise --namespace=flowise-production

      - name: Run smoke tests
        run: |
          # Run smoke tests against production
          npm run test:smoke -- --url=https://your-domain.com

      - name: Notify deployment
        run: |
          curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"Flowise deployed to production"}' \
            ${{ secrets.SLACK_WEBHOOK }}

⚡ Performance Optimization

Caching Strategies

// Multi-level caching system
class CacheManager {
  constructor(redisClient, memoryCache) {
    this.redis = redisClient;
    this.memory = memoryCache;
  }

  async get(key, fetchFunction = null, ttl = 300) {
    // Check memory cache first
    let value = this.memory.get(key);
    if (value !== undefined) {
      return value;
    }

    // Check Redis cache
    value = await this.redis.get(key);
    if (value !== null) {
      // Populate memory cache
      this.memory.set(key, value, ttl);
      return JSON.parse(value);
    }

    // Fetch from source
    if (fetchFunction) {
      value = await fetchFunction();

      // Cache the result
      await this.set(key, value, ttl);

      return value;
    }

    return null;
  }

  async set(key, value, ttl = 300) {
    const serialized = JSON.stringify(value);

    // Set in both caches
    this.memory.set(key, value, ttl);
    await this.redis.setex(key, ttl, serialized);
  }

  async invalidate(pattern = '*') {
    // Invalidate memory cache
    this.memory.clear();

    // Invalidate Redis cache
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(keys);
    }
  }

  // Workflow-specific caching
  async cacheWorkflowResult(workflowId, input, result, ttl = 3600) {
    const cacheKey = `workflow:${workflowId}:${this.hashInput(input)}`;
    await this.set(cacheKey, result, ttl);
  }

  async getCachedWorkflowResult(workflowId, input) {
    const cacheKey = `workflow:${workflowId}:${this.hashInput(input)}`;
    return await this.get(cacheKey);
  }

  private hashInput(input) {
    // Create a hash of the input for cache key
    const crypto = require('crypto');
    return crypto.createHash('md5').update(JSON.stringify(input)).digest('hex');
  }
}

Database Optimization

// Database connection pooling and optimization
const { Pool } = require('pg');

class DatabaseOptimizer {
  constructor() {
    this.pool = new Pool({
      host: process.env.DB_HOST,
      port: process.env.DB_PORT,
      database: process.env.DB_NAME,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      max: parseInt(process.env.DB_MAX_CONNECTIONS) || 20,
      min: parseInt(process.env.DB_MIN_CONNECTIONS) || 5,
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
      acquireTimeoutMillis: 60000,
    });

    // Set up pool event handlers
    this.pool.on('connect', (client) => {
      console.log('New client connected to database');
    });

    this.pool.on('error', (err, client) => {
      console.error('Database pool error:', err);
    });
  }

  async query(text, params = []) {
    const start = Date.now();
    const client = await this.pool.connect();

    try {
      const result = await client.query(text, params);
      const duration = Date.now() - start;

      // Log slow queries
      if (duration > 1000) {
        console.warn(`Slow query (${duration}ms):`, text);
      }

      return result;
    } finally {
      client.release();
    }
  }

  async healthCheck() {
    try {
      await this.query('SELECT 1');
      return { status: 'healthy' };
    } catch (error) {
      return { status: 'unhealthy', error: error.message };
    }
  }

  async getStats() {
    return {
      totalCount: this.pool.totalCount,
      idleCount: this.pool.idleCount,
      waitingCount: this.pool.waitingCount
    };
  }

  async close() {
    await this.pool.end();
  }
}

💰 Cost Optimization

Usage Monitoring and Quotas

// Usage tracking and cost optimization
class UsageMonitor {
  constructor(database, billingService) {
    this.db = database;
    this.billing = billingService;
    this.usageStats = new Map();
  }

  async trackWorkflowExecution(workflowId, executionId, metadata) {
    const usage = {
      workflowId,
      executionId,
      timestamp: new Date(),
      nodeCount: metadata.nodeCount,
      tokenCount: metadata.tokenCount,
      apiCalls: metadata.apiCalls,
      duration: metadata.duration,
      cost: await this.calculateCost(metadata)
    };

    // Store usage data
    await this.db.insert('workflow_usage', usage);

    // Update real-time stats
    this.updateUsageStats(workflowId, usage);

    // Check quotas
    await this.checkQuotas(workflowId, usage);

    return usage;
  }

  async calculateCost(metadata) {
    let totalCost = 0;

    // LLM API costs
    if (metadata.llmUsage) {
      for (const [provider, usage] of Object.entries(metadata.llmUsage)) {
        totalCost += this.calculateLLMCost(provider, usage);
      }
    }

    // External API costs
    if (metadata.apiCalls) {
      totalCost += metadata.apiCalls * 0.01; // $0.01 per API call
    }

    // Storage costs
    if (metadata.storageBytes) {
      totalCost += (metadata.storageBytes / (1024 * 1024 * 1024)) * 0.10; // $0.10 per GB
    }

    return totalCost;
  }

  calculateLLMCost(provider, usage) {
    const rates = {
      'openai-gpt4': 0.03,      // $0.03 per 1K tokens
      'openai-gpt3.5': 0.002,   // $0.002 per 1K tokens
      'anthropic-claude': 0.015 // $0.015 per 1K tokens
    };

    const rate = rates[provider] || 0.01;
    return (usage.tokens / 1000) * rate;
  }

  updateUsageStats(workflowId, usage) {
    if (!this.usageStats.has(workflowId)) {
      this.usageStats.set(workflowId, {
        executions: 0,
        totalCost: 0,
        totalTokens: 0,
        lastExecution: null
      });
    }

    const stats = this.usageStats.get(workflowId);
    stats.executions++;
    stats.totalCost += usage.cost;
    stats.totalTokens += usage.tokenCount || 0;
    stats.lastExecution = usage.timestamp;
  }

  async checkQuotas(workflowId, usage) {
    const quotas = await this.getQuotas(workflowId);

    // Check execution limits
    if (quotas.maxExecutions && this.usageStats.get(workflowId)?.executions > quotas.maxExecutions) {
      throw new QuotaExceededError('Maximum executions exceeded');
    }

    // Check cost limits
    if (quotas.maxCost && this.usageStats.get(workflowId)?.totalCost > quotas.maxCost) {
      throw new QuotaExceededError('Cost limit exceeded');
    }

    // Check token limits
    if (quotas.maxTokens && this.usageStats.get(workflowId)?.totalTokens > quotas.maxTokens) {
      throw new QuotaExceededError('Token limit exceeded');
    }
  }

  async getUsageReport(workflowId, timeRange = '30d') {
    const endDate = new Date();
    const startDate = new Date();

    // Parse time range
    const days = parseInt(timeRange.replace('d', ''));
    startDate.setDate(endDate.getDate() - days);

    const usage = await this.db.query(
      'SELECT * FROM workflow_usage WHERE workflow_id = ? AND timestamp BETWEEN ? AND ?',
      [workflowId, startDate, endDate]
    );

    return {
      totalExecutions: usage.length,
      totalCost: usage.reduce((sum, u) => sum + u.cost, 0),
      totalTokens: usage.reduce((sum, u) => sum + u.token_count, 0),
      averageDuration: usage.reduce((sum, u) => sum + u.duration, 0) / usage.length,
      usageByDay: this.groupByDay(usage)
    };
  }

  groupByDay(usage) {
    const grouped = {};

    usage.forEach(item => {
      const date = item.timestamp.toISOString().split('T')[0];
      if (!grouped[date]) {
        grouped[date] = { executions: 0, cost: 0, tokens: 0 };
      }
      grouped[date].executions++;
      grouped[date].cost += item.cost;
      grouped[date].tokens += item.token_count || 0;
    });

    return grouped;
  }
}

🧪 Hands-On Exercise

Estimated Time: 90 minutes

  1. Docker Production Setup: Create and configure Docker containers for production deployment
  2. Monitoring Implementation: Set up Prometheus metrics and Grafana dashboards
  3. Kubernetes Deployment: Deploy Flowise to Kubernetes with proper resource limits
  4. CI/CD Pipeline: Create automated deployment pipeline with testing
  5. Performance Optimization: Implement caching and database optimization
  6. Cost Monitoring: Set up usage tracking and cost optimization

🎉 Congratulations! You've completed the comprehensive Flowise LLM Orchestration Platform Deep Dive tutorial. You now have the knowledge to build, deploy, and maintain production-grade LLM workflow applications.

🎯 What You've Learned

  1. Workflow Engine: Deep understanding of Flowise's execution pipeline and optimization
  2. Node Development: Creating custom nodes with advanced functionality and error handling
  3. Advanced Integrations: Multi-provider LLMs, complex data routing, and enterprise systems
  4. Production Deployment: Docker, Kubernetes, monitoring, and scaling strategies
  5. Performance & Cost: Optimization techniques and usage monitoring for production workloads

🚀 Next Steps

  • Build Enterprise Workflows: Create complex multi-step AI workflows for business processes
  • Custom Node Ecosystem: Develop and share custom nodes for specific use cases
  • Integration Platforms: Connect Flowise with existing enterprise systems
  • Advanced Monitoring: Implement comprehensive observability and alerting
  • Cost Optimization: Monitor and optimize AI workflow costs at scale

Happy orchestrating! 🤖✨

What Problem Does This Solve?

Most teams struggle here because the hard part is not writing more code, but deciding clear boundaries for flowise, name, usage so behavior stays predictable as complexity grows.

In practical terms, this chapter helps you avoid three common failures:

  • coupling core logic too tightly to one implementation path
  • missing the handoff boundaries between setup, execution, and validation
  • shipping changes without clear rollback or observability strategy

After working through this chapter, you should be able to reason about Chapter 5: Production Deployment as an operating subsystem inside Flowise LLM Orchestration: Deep Dive Tutorial, with explicit contracts for inputs, state transitions, and outputs.

Use the implementation notes around workflowId, production, metadata as your checklist when adapting these patterns to your own repository.

Production Deployment Architecture

flowchart TD
    A[Flowise Docker image] --> B[docker-compose or K8s]
    B --> C{Storage backend}
    C -->|SQLite| D[Single node deployment]
    C -->|PostgreSQL| E[HA multi-node deployment]
    D --> F[Flowise server :3000]
    E --> F
    F --> G[Nginx / load balancer]
    G --> H[API clients / UI users]
    F --> I[Vector store connections]
    F --> J[LLM provider APIs]
Loading

How it Works Under the Hood

Under the hood, Chapter 5: Production Deployment usually follows a repeatable control path:

  1. Context bootstrap: initialize runtime config and prerequisites for flowise.
  2. Input normalization: shape incoming data so name receives stable contracts.
  3. Core execution: run the main logic branch and propagate intermediate state through usage.
  4. Policy and safety checks: enforce limits, auth scopes, and failure boundaries.
  5. Output composition: return canonical result payloads for downstream consumers.
  6. Operational telemetry: emit logs/metrics needed for debugging and performance tuning.

When debugging, walk this sequence in order and confirm each stage has explicit success/failure conditions.

Source Walkthrough

Use the following upstream sources to verify implementation details while reading this chapter:

  • Flowise Why it matters: authoritative reference on Flowise (github.com).

Suggested trace strategy:

  • search upstream code for flowise and name to map concrete implementation paths
  • compare docs claims against actual runtime/config code before reusing patterns in production

Chapter Connections