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
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.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
- SignalR Backpressure: Use
IAsyncEnumerablefor streaming to avoid client buffer overflow - ClickHouse Partitioning: Monthly partitions + 90-day TTL keeps storage manageable
- AI Cost Control: Complexity-based routing is more effective than random sampling
- Real-Time + Analytics: Separating concerns (SignalR for real-time, ClickHouse for analytics) scales better than a unified solution
Related Posts
Distributed Multi-Agent Consensus: Implementing Raft for Real-Time AI Reasoning
Building K2-Consensus, a real-time reasoning team using distributed consensus algorithms for multi-agent coordination, leader election, and fault-tolerant decision making.
Polyglot Microservices on GKE: Building MedInsight's AI-Powered Diagnostics Platform
Deep dive into MedInsight's Go/Python polyglot architecture on Google Kubernetes Engine with Gemma 2 reasoning and HIPAA-compliant medical diagnostics achieving 89%+ test coverage.