Back to Blog
| 5 min read

Building Scalable AI Services: The Service Bus + Background Worker Pattern

If you've ever built an application that integrates AI services, you've probably encountered this frustrating scenario: your users upload a document for analysis, your API calls Azure OpenAI or Document Intelligence, and then... timeout.

.NET

How to eliminate timeouts and build production-ready AI applications that scale


The AI Timeout Problem

If you've ever built an application that integrates AI services, you've probably encountered this frustrating scenario: your users upload a document for analysis, your API calls Azure OpenAI or Document Intelligence, and then... timeout.

AI services are powerful, but they're also slow. Document extraction can take 30+ seconds, image classification might take a minute, and complex language models can process for even longer. Traditional synchronous web APIs simply can't handle these long-running operations without timing out or blocking other requests.

The Solution: Asynchronous Processing with Service Bus

The answer isn't to increase timeout values or ask users to wait indefinitely. Instead, we need to decouple the request from the processing using an asynchronous pattern that's become the gold standard for AI applications.

Here's how this architecture works:

1. Immediate Response ⚑

When a user uploads a file, your Web API controller:

  • Saves the file to blob storage
  • Creates database records
  • Sends a message to Azure Service Bus
  • Returns immediately with a 200 OK response

No waiting. No timeouts. Users get instant feedback that their request was received.

2. Background Processing πŸ”„

A background worker service:

  • Polls the Service Bus queue
  • Downloads files from blob storage
  • Calls AI services (OpenAI, Document Intelligence, etc.)
  • Saves results back to the database
  • Handles retries and error scenarios

3. Status Tracking πŸ“Š

Users can check processing status through a simple API:

GET /api/extractions/{id}/status

The response shows whether processing is pending, in progress, complete, or failed.

Why This Pattern Works So Well for AI

This architecture solves multiple problems that plague AI applications:

🚫 No Timeouts
Web requests return immediately, eliminating timeout issues entirely.

πŸ“ˆ Horizontal Scaling
Need to process more documents? Just add more worker instances.

πŸ”„ Built-in Retry Logic
Service Bus automatically retries failed messages with exponential backoff.

πŸ’° Cost Optimization
Process workloads when resources are available, not when users happen to upload files.

⚑ Rate Limiting
Queuing naturally prevents overwhelming AI services with too many concurrent requests.

πŸ›‘οΈ Fault Tolerance
If a worker crashes, messages remain in the queue and get processed by other workers.

Real-World Applications

This pattern works beautifully for any AI service that takes more than a few seconds:

Document Processing

  • Contract extraction and analysis
  • Invoice processing and data entry
  • Resume parsing for HR systems
  • Legal document review

Image & Video Analysis

  • OCR text extraction from images
  • Medical image analysis
  • Video transcription and captioning
  • Object detection and classification

Natural Language Processing

  • Large document summarization
  • Sentiment analysis on bulk data
  • Language translation of lengthy content
  • Entity extraction from research papers

Custom ML Models

  • Fraud detection on transaction batches
  • Recommendation engine training
  • Predictive analytics on large datasets
  • Classification of complex data types

Implementation Made Simple

The beauty of this pattern is that once you build it once, you can reuse it for any AI service. Here's the basic flow in code:

[HttpPost("upload")]
public async Task<IActionResult> ProcessDocument(IFormFile file)
{
    // 1. Save file and create records
    var uploadId = await SaveFileAndCreateRecords(file);
    
    // 2. Send message to Service Bus
    await _serviceBus.EnqueueAsync(new AIProcessingMessage 
    {
        UploadId = uploadId,
        ServiceType = "document-extraction",
        BlobUrl = blobUrl
    });
    
    // 3. Return immediately
    return Ok(new { 
        message = "Upload successful, processing in background",
        statusUrl = $"/api/status/{uploadId}"
    });
}

The background worker handles the heavy lifting:

private async Task ProcessMessage(AIProcessingMessage message)
{
    // Update status to "Processing"
    await UpdateStatus(message.UploadId, "Processing");
    
    // Call AI service (this takes 30+ seconds)
    var results = await _aiService.ProcessAsync(message.BlobUrl);
    
    // Save results and update status to "Complete"
    await SaveResults(message.UploadId, results);
}

The Bottom Line

Building AI applications doesn't have to be painful. By embracing asynchronous processing with Azure Service Bus and background workers, you can create applications that are:

  • Responsive for users (no waiting around)
  • Scalable for your business (handle any workload)
  • Reliable in production (fault tolerance built-in)
  • Cost-effective to operate (optimize resource usage)

This isn't just a nice-to-have patternβ€”it's essential for any serious AI application. Your users will thank you for the smooth experience, and your infrastructure team will thank you for building something that actually scales.

AI timeout headaches? Start with Service Bus, add a background worker.

Tags

#Software Engineering #System Design #Intermediate
Casey Spaulding
Casey Spaulding

.NET Full Stack Engineer. 21 years in the Navy, then software.

Ask Casey AI

Ask Me Anything

Hi! I'm Casey's AI assistant. Ask me anything about his work, skills, or projects!

10 messages remaining