Back to The Vault

Real-Time AI Observability: Building AzureDevNexus with .NET 9 and SignalR

Deep dive into AzureDevNexus's intelligent developer ecosystem featuring AI-powered code review with Azure OpenAI, real-time collaboration via SignalR, and high-performance analytics with ClickHouse.

Cite this page
Kapoor, Saksham. "Real-Time AI Observability: Building AzureDevNexus with .NET 9 and SignalR." The Vault (blog). September 15, 2025. https://saksham-kapoor.vercel.app/vault/realtime-ai-observability
September 15, 2025Updated October 20, 20256 min read

North Star Metric: Sub-100ms Real-Time Message Delivery

AzureDevNexus achieves sub-100ms p95 message delivery for real-time collaboration while processing 1M+ observability events/hour through ClickHouse analytics.

System Architecture

The ClickHouse Decision

Trade-off: ClickHouse vs Azure SQL for Analytics
Decision: ClickHouse for observability data, Azure SQL for transactional data
  • Azure SQL for everything: Simpler architecture, but analytics queries on 1M+ events would take 30+ seconds
  • ClickHouse for analytics: Sub-second queries on billions of rows, columnar storage optimized for aggregations
  • Hybrid approach: Azure SQL for code reviews and user data, ClickHouse for observability metrics
  • Result: 100x faster analytics queries while maintaining ACID guarantees for critical data

Real-Time Collaboration with SignalR

Hub Implementation

using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;

public class CollaborationHub : Hub
{
    private static readonly ConcurrentDictionary<string, HashSet<string>> 
        _projectConnections = new();
    
    private readonly ICodeReviewService _reviewService;
    private readonly ILogger<CollaborationHub> _logger;
    
    public CollaborationHub(
        ICodeReviewService reviewService,
        ILogger<CollaborationHub> logger)
    {
        _reviewService = reviewService;
        _logger = logger;
    }
    
    public async Task JoinProject(string projectId)
    {
        var connectionId = Context.ConnectionId;
        var userName = Context.User?.Identity?.Name ?? "Anonymous";
        
        // Add to SignalR group
        await Groups.AddToGroupAsync(connectionId, projectId);
        
        // Track connection
        _projectConnections.AddOrUpdate(
            projectId,
            _ => new HashSet<string> { connectionId },
            (_, set) => { set.Add(connectionId); return set; }
        );
        
        // Notify others
        await Clients.Group(projectId).SendAsync(
            "UserJoined",
            new { UserName = userName, Timestamp = DateTime.UtcNow }
        );
        
        _logger.LogInformation(
            "User {User} joined project {Project}", 
            userName, 
            projectId
        );
    }
    
    public async Task SendCodeChange(string projectId, CodeChange change)
    {
        // Validate change
        if (!await _reviewService.ValidateChangeAsync(change))
        {
            await Clients.Caller.SendAsync("ChangeRejected", change.Id);
            return;
        }
        
        // Broadcast to all project members except sender
        await Clients.OthersInGroup(projectId).SendAsync(
            "CodeChanged",
            new
            {
                change.Id,
                change.FilePath,
                change.Content,
                change.Range,
                Author = Context.User?.Identity?.Name,
                Timestamp = DateTime.UtcNow
            }
        );
    }
    
    public async Task RequestAIReview(string projectId, string fileContent, string language)
    {
        var connectionId = Context.ConnectionId;
        
        // Notify user that review is starting
        await Clients.Caller.SendAsync("ReviewStarted", DateTime.UtcNow);
        
        // Stream AI review results
        await foreach (var chunk in _reviewService.StreamReviewAsync(fileContent, language))
        {
            await Clients.Caller.SendAsync("ReviewChunk", chunk);
        }
        
        await Clients.Caller.SendAsync("ReviewCompleted", DateTime.UtcNow);
    }
    
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        var connectionId = Context.ConnectionId;
        
        // Clean up connection tracking
        foreach (var (projectId, connections) in _projectConnections)
        {
            if (connections.Remove(connectionId))
            {
                await Clients.Group(projectId).SendAsync(
                    "UserLeft",
                    new { ConnectionId = connectionId, Timestamp = DateTime.UtcNow }
                );
            }
        }
        
        await base.OnDisconnectedAsync(exception);
    }
}

AI-Powered Code Review

Hybrid AI Strategy

Constraint: Azure OpenAI Cost Optimization
Impact: Requires intelligent routing between GPT-4 and GPT-3.5
  • GPT-4 at 0.03/1KtokensvsGPT3.5at0.03/1K tokens vs GPT-3.5 at 0.002/1K tokens (15x cost difference)
  • Solution: Route security-critical reviews to GPT-4, routine checks to GPT-3.5
  • Implementation: Code complexity scoring determines routing
  • Result: 70% cost reduction while maintaining quality for critical reviews
public class HybridCodeReviewService : ICodeReviewService
{
    private readonly AzureOpenAIClient _gpt4Client;
    private readonly AzureOpenAIClient _gpt35Client;
    private readonly ICodeComplexityAnalyzer _complexityAnalyzer;
    
    public async IAsyncEnumerable<ReviewChunk> StreamReviewAsync(
        string code, 
        string language,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        // Analyze code complexity
        var complexity = await _complexityAnalyzer.AnalyzeAsync(code, language);
        
        // Route to appropriate model
        var client = ShouldUseGpt4(complexity) ? _gpt4Client : _gpt35Client;
        var modelName = ShouldUseGpt4(complexity) ? "gpt-4" : "gpt-35-turbo";
        
        yield return new ReviewChunk
        {
            Type = "metadata",
            Content = $"Using {modelName} for review (complexity: {complexity.Score})"
        };
        
        var prompt = BuildReviewPrompt(code, language, complexity);
        
        await foreach (var chunk in client.GetChatCompletionsStreamingAsync(
            new ChatCompletionsOptions
            {
                DeploymentName = modelName,
                Messages = { new ChatRequestUserMessage(prompt) },
                Temperature = 0.3f,
                MaxTokens = 2000
            },
            ct))
        {
            if (!string.IsNullOrEmpty(chunk.ContentUpdate))
            {
                yield return new ReviewChunk
                {
                    Type = "content",
                    Content = chunk.ContentUpdate
                };
            }
        }
    }
    
    private bool ShouldUseGpt4(CodeComplexity complexity)
    {
        // Use GPT-4 for:
        // - Security-sensitive code (auth, crypto, SQL)
        // - High cyclomatic complexity (> 15)
        // - Files with known CVE patterns
        return complexity.HasSecurityPatterns ||
               complexity.CyclomaticComplexity > 15 ||
               complexity.HasCvePatterns;
    }
    
    private string BuildReviewPrompt(string code, string language, CodeComplexity complexity)
    {
        var focusAreas = new List<string> { "code quality", "best practices" };
        
        if (complexity.HasSecurityPatterns)
            focusAreas.Add("security vulnerabilities (OWASP Top 10)");
        
        if (complexity.CyclomaticComplexity > 10)
            focusAreas.Add("complexity reduction opportunities");
        
        // Build prompt with code block for AI review
        var sb = new StringBuilder();
        sb.AppendLine($"Review the following {language} code focusing on: {string.Join(", ", focusAreas)}");
        sb.AppendLine();
        sb.AppendLine(code);
        sb.AppendLine();
        sb.AppendLine("Provide: Critical issues, Warnings, Suggestions, Security concerns");
        sb.AppendLine("Format: [SEVERITY] Category: Description - Location: line X - Recommendation");
        
        return sb.ToString();
    }
}

ClickHouse Analytics Pipeline

Schema Design

-- Observability events table (optimized for time-series queries)
CREATE TABLE code_review_events
(
    event_id UUID,
    timestamp DateTime64(3),
    project_id String,
    user_id String,
    event_type LowCardinality(String),
    file_path String,
    language LowCardinality(String),
    model_used LowCardinality(String),
    tokens_used UInt32,
    latency_ms UInt32,
    issues_found UInt16,
    severity_critical UInt8,
    severity_warning UInt8,
    severity_info UInt8,
    cost_usd Decimal(10, 6)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (project_id, timestamp)
TTL timestamp + INTERVAL 90 DAY;

-- Materialized view for real-time dashboards
CREATE MATERIALIZED VIEW code_review_hourly_stats
ENGINE = SummingMergeTree()
ORDER BY (project_id, hour)
AS SELECT
    project_id,
    toStartOfHour(timestamp) AS hour,
    count() AS review_count,
    sum(tokens_used) AS total_tokens,
    sum(latency_ms) AS total_latency_ms,
    sum(issues_found) AS total_issues,
    sum(cost_usd) AS total_cost
FROM code_review_events
GROUP BY project_id, hour;

Query Performance

public class ClickHouseAnalyticsService
{
    private readonly ClickHouseConnection _connection;
    
    public async Task<ProjectAnalytics> GetProjectAnalyticsAsync(
        string projectId, 
        DateRange range)
    {
        // This query processes millions of events in under 100ms
        var sql = @"
            SELECT
                count() as review_count,
                avg(latency_ms) as avg_latency,
                sum(issues_found) as total_issues,
                sum(cost_usd) as total_cost,
                countIf(model_used = 'gpt-4') as gpt4_reviews,
                countIf(model_used = 'gpt-35-turbo') as gpt35_reviews,
                quantile(0.95)(latency_ms) as p95_latency
            FROM code_review_events
            WHERE project_id = @projectId
              AND timestamp BETWEEN @start AND @end
        ";
        
        await using var cmd = _connection.CreateCommand();
        cmd.CommandText = sql;
        cmd.Parameters.AddWithValue("projectId", projectId);
        cmd.Parameters.AddWithValue("start", range.Start);
        cmd.Parameters.AddWithValue("end", range.End);
        
        await using var reader = await cmd.ExecuteReaderAsync();
        // ... map to ProjectAnalytics
    }
}

Performance Results

  • SignalR Latency: p95 under 100ms message delivery
  • Concurrent Connections: 1000+ per project
  • ClickHouse Query Time: under 100ms for 1M+ events
  • AI Review Latency: p95 under 3s (GPT-4), under 1s (GPT-3.5)
  • Cost Optimization: 70% reduction via hybrid AI routing
  • Uptime: 99.9% availability

Key Learnings

  1. SignalR Backpressure: Use IAsyncEnumerable for streaming to avoid client buffer overflow
  2. ClickHouse Partitioning: Monthly partitions + 90-day TTL keeps storage manageable
  3. AI Cost Control: Complexity-based routing is more effective than random sampling
  4. Real-Time + Analytics: Separating concerns (SignalR for real-time, ClickHouse for analytics) scales better than a unified solution

Related Posts

Press +K to search