The Bulkhead Pattern: Isolating Failures Between Subsystems

Modern systems are rarely monolithic anymore. They’re composed of APIs, background jobs, databases, external integrations, and shared infrastructure. While this modularity enables scale, it also introduces a risk that’s easy to underestimate:

A failure in one part of the system can cascade and take everything down.

The Bulkhead pattern exists to prevent exactly that.


Where the Name Comes From

The term bulkhead comes from ship design.

Ships are divided into watertight compartments. If one compartment floods, the damage is contained and the ship stays afloat.

In software, the idea is the same:

Partition your system so failures are isolated and do not spread.

Instead of one failure sinking the entire application, only a portion is affected.


The Core Problem Bulkheads Solve

In many systems, subsystems unintentionally share critical resources:

  • Thread pools
  • Database connection pools
  • Memory
  • CPU
  • Network bandwidth
  • External API quotas

When one subsystem misbehaves—slow queries, infinite retries, traffic spikes—it can exhaust shared resources and starve healthy parts of the system.

This leads to:

  • Cascading failures
  • System-wide outages
  • “Everything is down” incidents caused by one weak link

What “Applying the Bulkhead Pattern” Means

When you apply the Bulkhead pattern, you intentionally isolate resources so that:

  • A failure in Subsystem A
  • Cannot exhaust or block resources used by Subsystem B

The goal is failure containment, not failure prevention.

Failures still happen—but they stay local.


A Simple Example

Without Bulkheads

  • Public API and background jobs share:
    • The same App Service
    • The same thread pool
    • The same database connection pool

A spike in background processing:

  • Consumes threads
  • Exhausts DB connections
  • Causes API requests to hang

Result: Total outage


With Bulkheads

  • Public API runs independently
  • Background jobs run in a separate process or service
  • Each has its own execution and scaling limits

Background jobs fail or slow down
API continues serving users

Result: Partial degradation, not total failure


Common Places to Apply Bulkheads

1. Service-level isolation

  • Separate services for:
    • Public APIs
    • Admin APIs
    • Background processing
  • Independent scaling and deployments

This is the most visible form of bulkheading.


2. Execution and thread isolation

  • Dedicated worker pools
  • Separate queues for different workloads
  • Isolation between synchronous and asynchronous processing

This prevents noisy workloads from starving critical paths.


3. Dependency isolation

  • Separate databases or schemas per workload
  • Read replicas for reporting
  • Independent external API clients with their own timeouts and retries

A slow dependency should not block unrelated operations.


4. Rate and quota isolation

  • Per-tenant throttling
  • Per-client limits
  • Separate API routes with different rate policies

Abuse or spikes from one consumer don’t impact others.


Cloud-Native Bulkheads (Real-World Examples)

You may already be using the Bulkhead pattern without explicitly naming it.

  • Web APIs separated from background jobs
  • Reporting workloads isolated from transactional databases
  • Admin endpoints deployed separately from public endpoints
  • Async processing moved to queues instead of inline execution

All of these are bulkheads in practice.


Bulkhead vs Circuit Breaker (Quick Clarification)

These patterns are often mentioned together, but they solve different problems:

  • Bulkhead pattern
    Prevents failures from spreading by isolating resources
  • Circuit breaker pattern
    Stops calling a dependency that is already failing

Think of bulkheads as structural isolation and circuit breakers as runtime protection.

Used together, they significantly improve system resilience.


Why This Pattern Matters in Production

Bulkheads:

  • Reduce blast radius
  • Turn outages into degradations
  • Protect critical user paths
  • Make systems predictable under stress

Most large-scale outages aren’t caused by a single bug—they’re caused by uncontained failures.

Bulkheads give you containment.


A Practical Mental Model

A simple way to reason about the pattern:

“What happens to the rest of the system if this component misbehaves?”

If the answer is “everything slows down or crashes”, you probably need a bulkhead.


Final Thoughts

The Bulkhead pattern isn’t about adding complexity—it’s about intentional boundaries.

You don’t need microservices everywhere.
You don’t need perfect isolation.

But you do need to decide:

  • Which failures are acceptable
  • Which paths must stay alive
  • Which resources must never be shared

Applied thoughtfully, bulkheads are one of the most effective tools for building systems that survive real-world conditions.

Bulkhead Pattern in Azure (Practical Examples)

Azure makes it relatively easy to apply the Bulkhead pattern because many services naturally enforce isolation boundaries.

Here are common, production-proven ways bulkheads show up in Azure architectures:

1. Separate compute for different workloads

  • Public-facing APIs hosted in:
    • Azure App Service
    • Azure Container Apps
  • Background processing hosted in:
    • Azure Functions
    • WebJobs
    • Container Apps Jobs

Each workload:

  • Scales independently
  • Has its own CPU, memory, and execution limits

A failure or spike in background processing does not starve user-facing traffic.


2. Queue-based isolation with Azure Storage or Service Bus

Using:

  • Azure Storage Queues
  • Azure Service Bus

…creates a natural bulkhead between:

  • Request handling
  • Long-running or unreliable work

If downstream processing slows or fails:

  • Messages accumulate
  • The API remains responsive

This is one of the most effective bulkheads in cloud-native systems.


3. Database workload separation

Common Azure patterns include:

  • Primary database for transactional workloads
  • Read replicas or secondary databases for reporting
  • Separate databases or schemas for batch jobs

Heavy analytics or reporting queries can no longer block critical application paths.


4. Rate limiting and ingress isolation

Using:

  • Azure API Management
  • Azure Front Door

You can enforce:

  • Per-client or per-tenant throttling
  • Separate rate policies for public vs admin APIs

This prevents abusive or noisy consumers from impacting the entire system.


5. Subscription and resource-level boundaries

At a higher level, bulkheads can also be enforced through:

  • Separate Azure subscriptions
  • Dedicated resource groups
  • Independent scaling and budget limits

This limits the blast radius of misconfigurations, cost overruns, or runaway workloads.


Why Azure Bulkheads Matter

In Azure, failures often come from:

  • Unexpected traffic spikes
  • Misbehaving background jobs
  • Cost-driven throttling
  • Shared service limits

Bulkheads turn these into localized incidents instead of platform-wide outages.

Sharing a Single Model Across Multiple Child Components in Blazor WebAssembly

There are several effective ways to share a single model (data object) between multiple child components in Blazor WebAssembly. Here are the best approaches:

1. Cascading Parameters (Best for hierarchical components)
<!-- ParentComponent.razor -->
@page "/parent"

<CascadingValue Value="@SharedModel">
    <ChildComponent1 />
    <ChildComponent2 />
</CascadingValue>

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

2. Component Parameters (Best for direct parent-child relationships)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" />
<ChildComponent2 Model="@SharedModel" />

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

3. State Management Service (Best for app-wide sharing)

// SharedModelService.cs
public class SharedModelService
{
    private MyModel _model = new();
    
    public MyModel Model 
    {
        get => _model;
        set
        {
            _model = value;
            NotifyStateChanged();
        }
    }
    
    public event Action OnChange;
    
    private void NotifyStateChanged() => OnChange?.Invoke();
}

Register the service in Program.cs:

builder.Services.AddSingleton<SharedModelService>();

Use in components:

@inject SharedModelService ModelService

@code {
    protected override void OnInitialized()
    {
        ModelService.OnChange += StateHasChanged;
    }
    
    private void UpdateModel()
    {
        ModelService.Model.Property = "New Value";
    }
}

4. EventCallback Pattern (For parent-child communication)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" ModelChanged="@HandleModelChanged" />
<ChildComponent2 Model="@SharedModel" ModelChanged="@HandleModelChanged" />

@code {
    private MyModel SharedModel { get; set; } = new();
    
    private void HandleModelChanged(MyModel updatedModel)
    {
        SharedModel = updatedModel;
        StateHasChanged(); // Refresh all components
    }
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
    
    [Parameter]
    public EventCallback<MyModel> ModelChanged { get; set; }
    
    private async Task UpdateModel()
    {
        Model.Property = "New Value";
        await ModelChanged.InvokeAsync(Model);
    }
}

5. Fluxor/Redux Pattern (For complex state management)

// Install package
dotnet add package Fluxor.Blazor.Web

// Define state
public record MyModelState
{
    public MyModel Model { get; init; } = new();
}

// Define actions
public record UpdateModelAction(MyModel Model);

// Create reducer
public static class Reducers
{
    [ReducerMethod]
    public static MyModelState ReduceUpdateModelAction(MyModelState state, UpdateModelAction action)
        => state with { Model = action.Model };
}

Use in components:

@inject IState<MyModelState> ModelState
@inject IDispatcher Dispatcher

<p>@ModelState.Value.Model.Property</p>

<button @onclick="UpdateModel">Update</button>

@code {
    private void UpdateModel()
    {
        var updatedModel = ModelState.Value.Model with { Property = "New Value" };
        Dispatcher.Dispatch(new UpdateModelAction(updatedModel));
    }
}

  1. For simple parent-child relationships: Use Component Parameters
  2. For deep component trees: Use Cascading Parameters
  3. For app-wide state: Use State Management Service or Fluxor
  4. For complex applications: Consider Fluxor/Redux pattern
  5. Immutable models: When sharing models, consider making them immutable or implementing proper change notifications

Performance Considerations

  • Avoid excessive re-rendering by implementing ShouldRender
  • Use [Parameter] public MyModel Model { get; set; } carefully as it can cause unnecessary renders
  • For large models, consider using view models or DTOs instead of full domain models

UBUNTU disk size increase

To increase disk size, first we need to see disk status;

df -h

If it’s a VM, make sure VM has allocated enough space before performing next actions.

Here’s the list of steps for a simple scenario where you have two partitions, /dev/sda1 is an ext4 partition the OS is booted from and /dev/sdb2 is swap. For this exercise we want to remove the swap partition an extend /dev/sda1 to the whole disk.

  1. As always, make sure you have a backup of your data – since we’re going to modify the partition table there’s a chance to lose all your data if you make a typo, for example.
  2. Run sudo fdisk /dev/sda
    • use p to list the partitions. Make note of the start cylinder of /dev/sda1
    • use d to delete first the swap partition (2) and then the /dev/sda1 partition. This is very scary but is actually harmless as the data is not written to the disk until you write the changes to the disk.
    • use n to create a new primary partition. Make sure its start cylinder is exactly the same as the old /dev/sda1 used to have. For the end cylinder agree with the default choice, which is to make the partition to span the whole disk.
    • use a to toggle the bootable flag on the new /dev/sda1
    • review your changes, make a deep breath and use w to write the new partition table to disk. You’ll get a message telling that the kernel couldn’t re-read the partition table because the device is busy, but that’s ok.
  3. Reboot with sudo reboot. When the system boots, you’ll have a smaller filesystem living inside a larger partition.
  4. The next magic command is resize2fs. Run sudo resize2fs /dev/sda1 – this form will default to making the filesystem to take all available space on the partition.

That’s it, we’ve just resized a partition on which Ubuntu is installed, without booting from an external drive.

https://askubuntu.com/questions/116351/increase-partition-size-on-which-ubuntu-is-installed