Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Tuesday

Scaling a Python application to serve millions of users

Scaling a Python application to serve millions of users requires moving past single-server setups and bypassing Python’s Global Interpreter Lock (GIL) through modern architecture. True scale is achieved by making the application stateless, caching aggressively, offloading heavy lifting, and ensuring the database never becomes a bottleneck.

1. Master Concurrency and Framework Selection

Python handles traffic differently depending on the chosen framework and runtime strategy:
  • Use Async Frameworks: Transition from synchronous frameworks (like standard Flask or Django) to asynchronous frameworks like FastAPI or Sanic. Async frameworks handle thousands of concurrent I/O-bound connections on a single process using an event loop.
  • Optimize WSGI/ASGI Servers: Run applications behind multi-process workers. For Django/Flask, use Gunicorn with or workers. For FastAPI, utilize Uvicorn with a defined number of worker processes to fully utilize multi-core CPU architectures.
2. Design for Horizontal Scaling

A single machine will always hit physical hardware limits.
  • Go Stateless: Never store user sessions, uploaded files, or state data directly on the application server memory. Move sessions to a shared cache and files to an object store like AWS S3.
  • Containerization: Package the app using Docker and manage it via Kubernetes. This allows automatic replication and auto-scaling of application instances based on real-time traffic spikes.
  • Reverse Proxy and Load Balancing: Deploy Nginx or HAProxy in front of application instances. They distribute the incoming million-user load evenly across your containerized fleet.
3. Eliminate Database Bottlenecks

The database is almost always the ultimate bottleneck in high-traffic applications.
  • Connection Pooling: Creating a database connection for every user request kills performance. Use connection poolers like PgBouncer for PostgreSQL to reuse existing connections safely.
  • Read/Write Splitting: Route all data mutations (Writes) to a primary database instance, and distribute all fetches (Reads) across multiple read-replicas.
  • Database Sharding or NoSQL: When data outgrows a single database, shard relational data across multiple databases, or transition high-volume, non-relational telemetry to NoSQL databases like Cassandra or MongoDB.
4. Implement a Strict Caching Layer

The fastest database query is the one you never have to make.
  • Application Caching: Implement Redis or Memcached directly in front of your database. Cache complex database queries, configuration settings, and user authorization tokens.
  • Edge Caching: Use a Content Delivery Network (CDN) like Cloudflare or Amazon CloudFront to cache and deliver static assets (images, JS, CSS) and API responses close to the user's geographic location.
5. Decouple via Asynchronous Task Queues

Never make a user wait for slow tasks during a standard HTTP request/response cycle.
  • Task Offloading: Send notification emails, process image uploads, or run analytics in the background.
  • Message Brokers: Use Celery combined with RabbitMQ or Apache Kafka to handle background job distributions safely across dedicated worker nodes.
Summary Architecture Blueprint

[ Million Users ] 
       │
       ▼
 [ Cloudflare CDN ]  ───(Serves Static Files)
       │
       ▼
 [ Nginx Load Balancer ]
       │
       ▼
 [ Kubernetes Pods (FastAPI / Gunicorn Workers) ]
       │
       ├───► [ Redis Cache ] (Fast Reads)
       │
       ├───► [ Celery Workers ] ──► [ Background Tasks ]
       │
       ▼
 [ PgBouncer / Database Cluster ] (Primary Write / Replica Reads)


 

Thursday

Python code examples for implementing an ML ensemble using Redis

 

Important Setup Note: RedisAI and RedisML are no longer actively maintained as of 2025/2026. Therefore, Pattern B has evolved into standard deployment patterns using lightweight modern tools (like ONNX Runtime or FastAPI workers coupled directly with Redis), ensuring your pipeline remains production-grade.

Pattern A: Asynchronous Parallel Processing (Redis Streams)

Best for: Heavy models (Deep Learning, large Random Forests) that need separate workers or hardware (GPUs) to compute in parallel without locking your main API.

Architecture Flow

[Client Request] 
       │
       ▼
 ┌───────────┐       XADD       ┌───────────────┐
 │  FastAPI  │ ────────────────>│ Redis Stream  │──┐ (Model 1 Worker)
 │  Gateway  │                  │(ml:requests)  │──┼─> [Model 2 Worker]
 └───────────┘                  └───────────────┘──┘ (Model 3 Worker)
       │                                                   │
       │ (Polls responses via                              │ XADD
       │  unique Request ID)                               ▼
       │                        ┌───────────────┐  ┌───────────────┐
       └────────────────────────│ Redis Hash    │<─│ Ensemble      │
                                │(ml:results:ID)│  │ Aggregator    │
                                └───────────────┘  └───────────────┘

Python Implementation

You will need redis and scikit-learn for this pattern. Run pip install redis scikit-learn.

Python
import time
import json
import uuid
import threading
import redis
from sklearn.linear_model import LogisticRegression
import numpy as np

# Connect to Redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 1. API GATEWAY FUNCTION (Pushes tasks, awaits final decision)
def mock_api_gateway(features):
    request_id = str(uuid.uuid4())
    payload = {"request_id": request_id, "features": json.dumps(features)}
    
    # Broadcast to the stream
    r.xadd("ml:requests", payload)
    print(f"[API] Dispatched request {request_id}")
    
    # Poll for the ensemble aggregator's final answer
    result_key = f"ml:results:{request_id}"
    while True:
        final_decision = r.hgetall(result_key)
        if final_decision:
            r.delete(result_key) # Clean up
            return final_decision
        time.sleep(0.01) # 10ms polling backoff

# 2. MODEL WORKERS (Simulating 2 different models processing concurrently)
def run_model_worker(model_name, weight_offset):
    # Dummy local model initialization
    model = LogisticRegression()
    
    while True:
        # Read from stream (simulating listening for real-time events)
        messages = r.xread({"ml:requests": "$"}, block=100)
        if not messages:
            continue
            
        for stream, msgs in messages:
            for msg_id, data in msgs:
                req_id = data["request_id"]
                features = json.loads(data["features"])
                
                # Mock prediction: calculate probability
                raw_prob = float(np.mean(features)) * weight_offset
                prob = min(max(raw_prob, 0.0), 1.0)
                
                # Write individual prediction back to a Redis Hash unique to this request
                r.hset(f"ml:preds:{req_id}", model_name, prob)

# 3. ENSEMBLE AGGREGATOR WORKER
def run_ensemble_aggregator():
    while True:
        # Scan for active prediction hashes
        for key in r.scan_iter("ml:preds:*"):
            req_id = key.split(":")[-1]
            predictions = r.hgetall(key)
            
            # Check if both expected model workers have responded
            if len(predictions) == 2:
                prob1 = float(predictions["model_alpha"])
                prob2 = float(predictions["model_beta"])
                
                # Ensemble Logic: Simple Average
                ensemble_prob = (prob1 + prob2) / 2
                decision = "Approved" if ensemble_prob > 0.5 else "Rejected"
                
                # Publish final outcome back to API
                r.hset(f"ml:results:{req_id}", mapping={
                    "decision": decision,
                    "probability": ensemble_prob
                })
                r.delete(key) # Clear intermediate predictions

# --- TEST EXECUTION LOOP ---
if __name__ == "__main__":
    # Flush old data for clean demo
    r.flushdb()
    
    # Start background threads for workers
    threading.Thread(target=run_model_worker, args=("model_alpha", 1.2), daemon=True).start()
    threading.Thread(target=run_model_worker, args=("model_beta", 0.8), daemon=True).start()
    threading.Thread(target=run_ensemble_aggregator, daemon=True).start()
    
    time.sleep(1) # Allow workers to boot
    
    # Simulate a client checking credit features
    mock_features = [0.4, 0.6, 0.55, 0.7] 
    output = mock_api_gateway(mock_features)
    print(f"[API] Final Ensemble Response: {output}")

Pattern B: Synchronous In-Memory Processing (Pipeline)

Best for: Light-to-medium models (like Scikit-Learn or XGBoost) where raw speed matters most. Instead of threading out across streams, your API queries a centralized Redis backend using a pipeline to get all required features at once, then locally runs predictions.

Architecture Flow

[Client Request] ──> [FastAPI Server]
                         │
                         ├─> Redis Pipeline (MGET Features) 
                         │       │
                         │       ▼
                         │   [Redis In-Memory Key Store]
                         │       │ (Returns user & context features <1ms)
                         ▼       ▼
                    Executes Model 1, 2, & 3 locally
                         │
                         ▼
                    Ensemble Aggregation ──> [Return Decision]

Python Implementation

Python
import redis
import json
import numpy as np

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Mock some feature store data inside Redis
r.hset("features:user_99", mapping={"risk_score": "0.34", "account_age_days": "120"})
r.hset("features:merchant_44", mapping={"fraud_velocity": "0.05", "trust_rating": "0.91"})

def predict_ensemble_sync(user_id, merchant_id):
    # 1. Pipeline Feature Store Retrieval (Single network round-trip)
    pipe = r.pipeline()
    pipe.hgetall(f"features:{user_id}")
    pipe.hgetall(f"features:{merchant_44}")
    user_feats, merchant_feats = pipe.execute()
    
    # Combine feature vectors
    base_features = [
        float(user_feats["risk_score"]),
        float(user_feats["account_age_days"]) / 365.0, # normalized
        float(merchant_feats["fraud_velocity"]),
        float(merchant_feats["trust_rating"])
    ]
    
    # 2. Synchronous Local Ensemble Execution
    # Model 1: Linear rule
    pred_1 = 1.0 if base_features[0] > 0.5 else 0.0
    # Model 2: Statistical weight
    pred_2 = (base_features[3] * 0.7) + (1 - base_features[2]) * 0.3
    
    # 3. Aggregation (Ensemble Rule: Weighted Voting)
    final_score = (pred_1 * 0.4) + (pred_2 * 0.6)
    return "Flagged Fraud" if final_score < 0.5 else "Authorized"

# Test synchronous run
decision = predict_ensemble_sync("user_99", "merchant_44")
print(f"[Sync API] Ensemble Outcome: {decision}")

Pattern C: Caching Decisions (The Shield)

Regardless of whether you use Pattern A or B, you should shield your models from processing repetitive inputs using an exact signature cache lookup.

Python
import hashlib
import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def get_ensemble_decision_with_cache(user_id, context_payload):
    # Generate a deterministic cache key based on inputs
    payload_string = json.dumps(context_payload, sort_keys=True)
    payload_hash = hashlib.sha256(payload_string.encode()).hexdigest()
    cache_key = f"cache:decision:{user_id}:{payload_hash}"
    
    # 1. Hit the cache layer
    cached_decision = r.get(cache_key)
    if cached_decision:
        print("[Cache] Match found! Zero model calculation needed.")
        return json.loads(cached_decision)
        
    # 2. Cache Miss: Run your actual ensemble pipeline (from Pattern A or B)
    print("[Cache] Miss. Running full ensemble inference...")
    computed_decision = {"status": "Approved", "confidence": 0.89}
    
    # 3. Save to cache with a 60-second Time-To-Live (TTL)
    r.setex(cache_key, 60, json.dumps(computed_decision))
    return computed_decision

# Test caching execution
payload = {"amount": 250.00, "location": "NY"}
print(get_ensemble_decision_with_cache("user_99", payload))
print(get_ensemble_decision_with_cache("user_99", payload)) # Secondary call hits cache

Building a Real-Time Feature Store with Redis This video outlines Redis' architectures, illustrating how it manages massive operational throughput for contextual data architectures in modern live-serving applications.

Wednesday

Maersk’s digital twin ecosystem

Maersk’s digital twin ecosystem integrates advanced algorithms, machine learning, AI, IoT sensor networks, and satellite connectivity for operational optimization, predictive analytics, and real-time decision-making; each component plays a specific technical role for their vessels and logistics platforms.

Algorithms and ML Techniques

Voyage Simulation Algorithms: Maersk’s digital twins simulate “ghost ship” voyages using data-driven algorithms that include time series analysis, regression models, and vessel hydrodynamics optimization; these help forecast fuel consumption, emissions, and routing efficiency before a voyage is booked.

Predictive Modeling: ML models (e.g. XGBoost, Random Forest, Neural Networks) are used to estimate future cargo demand, predict maintenance needs (predictive maintenance), detect anomalies (such as abnormal sensor readings), and optimize speed and course under varying weather and market conditions.metalab.

Prescriptive Analytics: Reinforcement learning and optimization algorithms help select the best speed and route for fuel and time savings, leveraging vessel, weather, and trade data.

Artificial Intelligence Capabilities

AI for Real-Time Operations: Computer vision, time-series forecasting, and optimization AIs continuously monitor sensor inputs, vessel status, and logistics data; these systems perform anomaly detection and health forecasting for engines, cargo holds, and supply networks.

Scenario Modeling: AI-enabled digital twins help planners run “what-if” scenarios: e.g., impact of supply chain disruptions, reroute simulations, and dynamic inventory management for resilience and risk mitigation.

Sustainability Optimization: AI models track and recommend operational changes to minimize emissions, optimize energy use, and schedule cargo transfers, enabling measurable carbon reduction and cost savings.

IoT Sensor and Edge Analytics

IoT Sensor Networks: Vessels are fitted with thousands of IoT sensors—engine, hull, weather, cargo, and environmental status—which stream real-time telemetry to Maersk’s analytics backend.

Edge Computing: Machine learning models run directly on vessels (“at the edge”), enabling instant analysis of fuel consumption, cargo condition, and climate impacts, often when vessels lack continuous shore connectivity.

AI-enabled Alerts: IoT analytics power automated risk detection and alerting—such as engine overheating, cargo temperature excursions, and hull damage—allowing prompt corrective action.

Satellite Connectivity and Communications

Global Satellite Networks: Maersk uses high-bandwidth Maritime satellite networks (Inmarsat) for fleet-wide, always-on connectivity for telemetry, cloud-based digital twin services, and crew welfare.

Cloud-Enabled Vessel Operations: Satellite links allow seamless data flows for real-time analytics, digital twin updates, and continuous crew and office communications, supporting both business processes and life aboard.

Remote Operations and Automation: Satellite connectivity unlocks “floating office” concepts a cloud-based applications, real-time fleet coordination, and even future autonomous vessel operations.

Example Data Pipeline Workflow

  • Technology

  • Operations

  • Role

  • Data collection

  • IoT sensors, AIS feedsMeasure fuel, speed, weather, cargo, ship location

  • Edge analytics Python ML models Forecast consumption, detect risks, local decisions

  • Cloud integration Satellite, cloud APIs

  • Sync vessel data, run digital twin simulations.

  • Predictive insights

  • ML/AI algorithms

  • Optimize routing, maintenance, emissions.

  • Prescriptive action

  • Optimization

  • AI Recommend speed/course, schedule, risk mitigations.

Maersk’s architecture blends data-driven AI, ML models, ubiquitous IoT analysis, and high-reliability satellite infrastructure to enable resilient, sustainable, and highly autonomous fleet and logistics management.

You can get more details here

Scaling a Python application to serve millions of users

Scaling a Python application to serve millions of users requires moving past single-server setups and bypassing Python’s Global Interpreter ...