Technology

C# Blazor Capacity Planning วางแผนรองรบผใชงาน Blazor Server

c blazor capacity planning
C# Blazor Capacity Planning | SiamCafe Blog
2025-07-29· อ. บอม — SiamCafe.net· 1,303 คำ

Blazor ?????????????????????

Blazor ???????????? web framework ????????? Microsoft ?????????????????? C# ????????? JavaScript ????????????????????????????????? interactive web UI ?????? 2 hosting models ????????????????????? Blazor Server ?????????????????????????????? server ????????? UI updates ???????????? SignalR WebSocket ????????? Blazor WebAssembly (WASM) ?????????????????????????????? browser ?????????????????????????????? WebAssembly runtime

Blazor Server ??????????????? initial load ???????????? app size ???????????? ????????????????????? server resources ?????????????????? ????????????????????? ?????????????????? connection ???????????????????????? latency ????????????????????? network server ????????????????????????????????? connection ????????? client Blazor WASM ??????????????? ????????????????????? client ????????????????????? connection ???????????? offline ????????? ????????????????????? initial load ????????????????????? download .NET runtime ?????? browser

Capacity Planning ?????????????????? Blazor ???????????????????????????????????????????????? Blazor Server ???????????????????????? client ????????? SignalR connection ?????????????????? memory ????????? CPU ?????? server ??????????????????????????????????????? server ??????????????????????????? concurrent users ????????????????????? resources ???????????????????????? scale ?????????????????????

????????????????????????????????????????????? Blazor Project

Setup Blazor project

# === Blazor Project Setup ===

# 1. Install .NET SDK
# Download from https://dotnet.microsoft.com/download
dotnet --version
# 8.0.xxx

# 2. Create Blazor Server App
dotnet new blazor -n MyBlazorApp --interactivity Server
cd MyBlazorApp

# 3. Project Structure
# MyBlazorApp/
# ????????? Components/
# ???   ????????? Layout/
# ???   ???   ????????? MainLayout.razor
# ???   ???   ????????? NavMenu.razor
# ???   ????????? Pages/
# ???   ???   ????????? Home.razor
# ???   ???   ????????? Counter.razor
# ???   ???   ????????? Weather.razor
# ???   ????????? _Imports.razor
# ???   ????????? App.razor
# ????????? wwwroot/
# ????????? Program.cs
# ????????? appsettings.json
# ????????? MyBlazorApp.csproj

# 4. Configure for Production
cat > appsettings.Production.json << 'EOF'
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "SignalR": {
    "MaximumReceiveMessageSize": 32768,
    "KeepAliveInterval": "00:00:15",
    "HandshakeTimeout": "00:00:15",
    "ClientTimeoutInterval": "00:00:30"
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=myapp;Trusted_Connection=true;"
  }
}
EOF

# 5. Run in Development
dotnet run --urls "https://localhost:5001"

# 6. Build for Production
dotnet publish -c Release -o ./publish
# Deploy ./publish folder to server

# 7. Docker Support
cat > Dockerfile << 'EOF'
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyBlazorApp.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyBlazorApp.dll"]
EOF

docker build -t myblazorapp .
docker run -d -p 8080:8080 myblazorapp

echo "Blazor project created"

Capacity Planning ?????????????????? Blazor

?????????????????? capacity ?????????????????? Blazor Server

// CapacityPlanner.cs ??? Blazor Server Capacity Planning
using System;
using System.Collections.Generic;

namespace MyBlazorApp.Planning
{
    public class CapacityPlanner
    {
        // Blazor Server Resource Estimates (per circuit/connection)
        // Memory: 250KB - 1MB per circuit (depends on component state)
        // CPU: minimal when idle, spikes on interactions
        // SignalR connection: 1 WebSocket per client
        
        public CapacityEstimate Calculate(int targetConcurrentUsers)
        {
            double memoryPerCircuitMB = 0.5; // 500KB average
            double cpuPerActiveUser = 0.002; // 0.2% CPU per active user
            double signalROverheadMB = 50; // Base SignalR overhead
            
            double totalMemoryMB = (targetConcurrentUsers * memoryPerCircuitMB) 
                                   + signalROverheadMB + 512; // 512MB base app
            double totalCPUCores = Math.Ceiling(
                targetConcurrentUsers * cpuPerActiveUser + 1); // +1 base core
            
            return new CapacityEstimate
            {
                ConcurrentUsers = targetConcurrentUsers,
                RecommendedMemoryGB = Math.Ceiling(totalMemoryMB / 1024 * 10) / 10,
                RecommendedCPUCores = (int)totalCPUCores,
                EstimatedBandwidthMbps = targetConcurrentUsers * 0.05,
                ServerInstances = CalculateInstances(targetConcurrentUsers),
                ScalingStrategy = GetScalingStrategy(targetConcurrentUsers),
            };
        }
        
        private int CalculateInstances(int users)
        {
            int usersPerInstance = 5000; // Conservative estimate
            return Math.Max(2, (int)Math.Ceiling((double)users / usersPerInstance));
        }
        
        private string GetScalingStrategy(int users)
        {
            if (users < 1000) return "Single server with failover";
            if (users < 5000) return "2-3 servers with load balancer (sticky sessions)";
            if (users < 20000) return "Auto-scaling group with Redis backplane";
            return "Kubernetes with HPA and Redis/Azure SignalR Service";
        }
    }
    
    public class CapacityEstimate
    {
        public int ConcurrentUsers { get; set; }
        public double RecommendedMemoryGB { get; set; }
        public int RecommendedCPUCores { get; set; }
        public double EstimatedBandwidthMbps { get; set; }
        public int ServerInstances { get; set; }
        public string ScalingStrategy { get; set; }
    }
    
    // Usage:
    // var planner = new CapacityPlanner();
    // var estimate = planner.Calculate(10000);
    // Console.WriteLine($"Memory: {estimate.RecommendedMemoryGB}GB");
    // Console.WriteLine($"CPU: {estimate.RecommendedCPUCores} cores");
    // Console.WriteLine($"Instances: {estimate.ServerInstances}");
    // Console.WriteLine($"Strategy: {estimate.ScalingStrategy}");
    //
    // Output for 10,000 users:
    // Memory: 5.6GB
    // CPU: 21 cores
    // Instances: 2
    // Strategy: Auto-scaling group with Redis backplane
}

Performance Optimization

???????????????????????????????????????????????? Blazor application

// Program.cs ??? Performance Configuration
using Microsoft.AspNetCore.ResponseCompression;

var builder = WebApplication.CreateBuilder(args);

// 1. SignalR Configuration for Scale
builder.Services.AddSignalR(options =>
{
    options.MaximumReceiveMessageSize = 32 * 1024; // 32KB
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
    options.HandshakeTimeout = TimeSpan.FromSeconds(15);
    options.EnableDetailedErrors = false; // Production
    options.MaximumParallelInvocationsPerClient = 1;
})
// Redis Backplane for multi-server
.AddStackExchangeRedis(o =>
{
    o.Configuration.ChannelPrefix = "BlazorApp";
});

// 2. Response Compression
builder.Services.AddResponseCompression(opts =>
{
    opts.EnableForHttps = true;
    opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/octet-stream" });
});

// 3. Output Caching
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5)));
    options.AddPolicy("StaticContent", builder =>
        builder.Expire(TimeSpan.FromHours(24)));
});

// 4. Connection Limits
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxConcurrentConnections = 10000;
    options.Limits.MaxConcurrentUpgradedConnections = 10000;
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
    options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
});

// 5. Memory Cache
builder.Services.AddMemoryCache(options =>
{
    options.SizeLimit = 1024; // entries
});

// 6. Health Checks
builder.Services.AddHealthChecks()
    .AddCheck("self", () => Microsoft.Extensions.Diagnostics.HealthChecks
        .HealthCheckResult.Healthy())
    .AddRedis("localhost:6379")
    .AddSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));

var app = builder.Build();

app.UseResponseCompression();
app.UseOutputCache();
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=604800");
    }
});

app.MapHealthChecks("/health");
app.MapRazorComponents().AddInteractiveServerRenderMode();

app.Run();

Load Testing ????????? Benchmarking

??????????????? load ?????????????????? Blazor Server

#!/usr/bin/env python3
# load_test_blazor.py ??? Load Testing for Blazor Server
import json
import logging
from datetime import datetime
from typing import Dict, List

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("loadtest")

class BlazorLoadTest:
    def __init__(self):
        self.results = []
    
    def k6_test_script(self):
        """k6 load test script for Blazor Server"""
        return """
// k6-blazor-test.js
import http from 'k6/http';
import ws from 'k6/ws';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

const errorRate = new Rate('errors');
const responseTime = new Trend('response_time');

export const options = {
    stages: [
        { duration: '2m', target: 100 },   // Ramp up
        { duration: '5m', target: 500 },   // Stay at 500
        { duration: '2m', target: 1000 },  // Peak
        { duration: '5m', target: 1000 },  // Sustain peak
        { duration: '2m', target: 0 },     // Ramp down
    ],
    thresholds: {
        http_req_duration: ['p(95)<500'],
        errors: ['rate<0.01'],
    },
};

export default function () {
    // 1. Load initial page
    const res = http.get('https://myapp.example.com/');
    check(res, { 'status is 200': (r) => r.status === 200 });
    responseTime.add(res.timings.duration);
    errorRate.add(res.status !== 200);
    
    // 2. Simulate SignalR connection
    const wsUrl = 'wss://myapp.example.com/_blazor';
    const wsRes = ws.connect(wsUrl, function (socket) {
        socket.on('open', () => {
            socket.send(JSON.stringify({ protocol: 'json', version: 1 }));
        });
        socket.on('message', (data) => {
            // Handle SignalR messages
        });
        sleep(10); // Keep connection for 10 seconds
        socket.close();
    });
    
    sleep(1);
}
"""
    
    def analyze_results(self):
        return {
            "test_date": datetime.utcnow().isoformat(),
            "scenarios": {
                "100_users": {
                    "avg_response_ms": 45,
                    "p95_response_ms": 120,
                    "p99_response_ms": 250,
                    "error_rate_pct": 0,
                    "memory_usage_mb": 890,
                    "cpu_usage_pct": 15,
                    "status": "PASS",
                },
                "500_users": {
                    "avg_response_ms": 85,
                    "p95_response_ms": 280,
                    "p99_response_ms": 450,
                    "error_rate_pct": 0.1,
                    "memory_usage_mb": 1850,
                    "cpu_usage_pct": 35,
                    "status": "PASS",
                },
                "1000_users": {
                    "avg_response_ms": 180,
                    "p95_response_ms": 520,
                    "p99_response_ms": 980,
                    "error_rate_pct": 0.5,
                    "memory_usage_mb": 3200,
                    "cpu_usage_pct": 65,
                    "status": "WARNING",
                },
                "2000_users": {
                    "avg_response_ms": 450,
                    "p95_response_ms": 1200,
                    "p99_response_ms": 2500,
                    "error_rate_pct": 2.5,
                    "memory_usage_mb": 5800,
                    "cpu_usage_pct": 90,
                    "status": "FAIL - need scaling",
                },
            },
            "recommendations": [
                "Current single server handles ~1000 concurrent comfortably",
                "Add Redis backplane and second server for 2000+ users",
                "Consider Azure SignalR Service for 5000+ users",
                "Enable response compression to reduce bandwidth 40%",
                "Use output caching for static pages",
            ],
        }

test = BlazorLoadTest()
results = test.analyze_results()
for scenario, data in results["scenarios"].items():
    print(f"{scenario}: {data['status']} (p95={data['p95_response_ms']}ms, mem={data['memory_usage_mb']}MB)")
print("\nRecommendations:", json.dumps(results["recommendations"], indent=2))

Monitoring ????????? Scaling

Monitor Blazor Server applications

# === Blazor Monitoring & Scaling ===

# 1. Kubernetes Deployment with HPA
cat > k8s/blazor-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blazor-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: blazor-app
  template:
    metadata:
      labels:
        app: blazor-app
    spec:
      containers:
        - name: blazor
          image: registry/blazor-app:v1
          ports:
            - containerPort: 8080
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: Production
            - name: ConnectionStrings__Redis
              value: redis-service:6379
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 2Gi
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: blazor-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: blazor-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
---
apiVersion: v1
kind: Service
metadata:
  name: blazor-service
spec:
  selector:
    app: blazor-app
  ports:
    - port: 80
      targetPort: 8080
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600
EOF

# 2. Nginx Ingress with Sticky Sessions
cat > k8s/ingress.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blazor-ingress
  annotations:
    nginx.ingress.kubernetes.io/affinity: cookie
    nginx.ingress.kubernetes.io/session-cookie-name: blazor-affinity
    nginx.ingress.kubernetes.io/session-cookie-max-age: "3600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/websocket-services: blazor-service
spec:
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: blazor-service
                port:
                  number: 80
EOF

echo "Kubernetes deployment configured"

FAQ ??????????????????????????????????????????

Q: Blazor Server ????????? Blazor WASM ??????????????????????????????????

A: ????????????????????????????????????????????????????????? Blazor Server ?????????????????????????????? ????????????????????? initial load ????????????, ???????????? access server resources (database, file system), ?????????????????????????????? offline support, ?????????????????? latency ????????? network roundtrip ???????????????????????? ???????????? maintain connection ????????????, server cost ???????????????????????? users ???????????? Blazor WASM ?????????????????????????????? ????????????????????? offline capability, ??????????????????????????? server load, app ??????????????? intensive ?????? client, ????????????????????? access server resources ???????????? ???????????????????????? initial load ????????? (download .NET runtime 2-5MB), ????????????????????????????????? SEO (????????????????????? prerendering) .NET 8 ?????????????????? Auto mode ???????????????????????????????????? Server ????????????????????????????????????????????? WASM ??????????????? download ???????????????

Q: Blazor Server ??????????????????????????? concurrent users?

A: ????????????????????? hardware ????????? application complexity ???????????????????????? ???????????????????????????????????? server 4 cores 8GB RAM ?????????????????? 3000-5000 concurrent circuits ?????????????????? app ??????????????? ????????? app ????????????????????? (???????????? state ????????????, render ????????????) ?????????????????????????????? 1000-2000 ???????????????????????????????????????????????????????????????????????? Memory per circuit (250KB-1MB), SignalR message processing, Component rendering server-side ?????????????????? scale ???????????? ????????? Azure SignalR Service ?????????????????? 100,000+ connections

Q: Sticky Sessions ????????????????????????????????????????????? Blazor Server?

A: ??????????????????????????? Blazor Server ????????? SignalR WebSocket connection ??????????????????????????? server instance ??????????????? ????????? load balancer ????????? request ??????????????? instance circuit ??????????????? ???????????? sticky sessions ????????? load balancer ???????????? cookie affinity ?????????????????? Kubernetes ???????????? sessionAffinity: ClientIP ????????? Service ???????????? nginx ingress annotation ?????????????????? cookie affinity ?????????????????? Azure SignalR Service ????????????????????? sticky sessions ??????????????? SignalR Service ?????????????????? connection routing ?????????

Q: Redis Backplane ????????????????????????????????????????

A: ??????????????? run Blazor Server ???????????? instances (scale out) ??????????????? instance ?????? SignalR hub ?????????????????? ????????? client A ???????????? instance 1 ????????? message ???????????????????????????????????? client B ?????? instance 2 ?????????????????????????????????????????????????????????????????? hub Redis Backplane ???????????????????????????????????????????????????????????? broadcast messages ???????????? instances ????????? instance subscribe Redis channel ????????????????????? message ???????????? Redis ????????????????????????????????? instance ????????? subscribe ???????????? ????????????????????????????????? run ????????????????????? 1 instance ????????? instance ?????????????????????????????????????????????

📖 บทความที่เกี่ยวข้อง

Redis Pub Sub Capacity Planningอ่านบทความ → PostgreSQL JSONB Capacity Planningอ่านบทความ → Java Virtual Threads Capacity Planningอ่านบทความ → Nuclei Scanner Capacity Planningอ่านบทความ → TTS Coqui Capacity Planningอ่านบทความ →

📚 ดูบทความทั้งหมด →