Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

3. Architecture

11 min read

The part most tutorials skip. Blob Storage looks like a bucket you throw files into; underneath it is a three-layer distributed system whose shape explains every limit, every 429, and every "why is this suddenly slow" question you will ever ask about it.

The three layers

Azure Storage is built as a stamp — a cluster of racks in a datacentre — with three layers stacked inside it:

Layer What it does What it explains
Front-end layer Terminates TLS, authenticates and authorises the request, parses it, and routes it to the right partition Why auth failures are fast and cheap, and where request-level throttling is applied
Partition layer Owns the namespace. Maps blob names to partitions, each served by one partition server, and keeps the index consistent Why the namespace is strongly consistent, why listing is ordered, and why a hot key range is a real problem
Stream layer An append-only distributed filesystem storing extents, replicated three ways within the stamp, with erasure coding for sealed extents Where durability comes from, and why writes are appends rather than in-place edits

Two consequences worth carrying:

Partitioning is by blob name, lexicographically. The partition key for a block blob is account/container/blobname. The service splits and merges ranges of that key space automatically based on load. A naming scheme where every write lands at the same end of the key space — a timestamp prefix like 2026-07-29T10:15:00-... — concentrates traffic on one partition range and caps your throughput at one partition server's ceiling, no matter how much the account allows. Randomising or hashing the leading characters of the key spreads it. This matters at high write rates, not at ten uploads a day.

Writes are appends into the stream layer, then a commit in the partition layer. That's why a block blob upload is "stage many blocks, then atomically commit the list": the blocks are already durable in the stream layer, and the commit is a single metadata operation. Until you commit, the blob doesn't exist to readers. Uncommitted blocks expire after a week ⚠️ verify current window against current Azure docs — and, until then, they are billed, which is where mysterious "storage used but no blobs" readings come from.

Tracing one upload, end to end

[Image Prompt: 2D minimalistic numbered sequence diagram tracing a blob upload from a client through Microsoft Entra ID token acquisition, DNS, the storage front-end layer, the partition layer, and the stream layer to three replicas, flat design, clean vector art style, white background]

  1. Get a token. The client's managed identity requests an access token for https://storage.azure.com/ from Microsoft Entra ID. (Or, in the legacy path, it computes an HMAC signature with the account key — no round trip, which is exactly why the key-based path is faster and more dangerous.)
  2. Resolve DNS. myaccount.blob.core.windows.net resolves to a public front-end VIP — unless a private endpoint exists, in which case a Private DNS zone (privatelink.blob.core.windows.net) overrides it with a private IP inside your VNet. This DNS override is the entire mechanism of Private Link, and it is what people forget when a private endpoint "doesn't work".
  3. TLS and network rules. The front end terminates TLS (enforcing the account's minimum TLS version) and evaluates the account firewall — allowed IP ranges, VNet service endpoints, private endpoint, trusted-services exception, and publicNetworkAccess. Blocked here, you get 403.
  4. AuthN/AuthZ. The front end validates the bearer token against Entra, then asks Azure RBAC whether this principal has the data action (Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write) at a scope covering this blob. Role assignments propagate in seconds-to-minutes, which is why a just-granted role sometimes still 403s.
  5. Route to a partition. The front end looks up which partition server owns this blob's key range and forwards the request.
  6. Stage blocks. For a large upload, the SDK splits the file into blocks and issues parallel Put Block calls. Each block is written to the stream layer and acknowledged only once it is durable on three replicas within the stamp — this is synchronous, and it is where LRS durability comes from.
  7. Commit. Put Block List names the blocks in order. The partition server updates the index atomically. The blob now exists, at full size, to every reader — there is no intermediate state where a reader sees half a blob.
  8. Replicate outward, asynchronously. If the account is GRS/GZRS, the write is queued for replication to the paired region. This lags, and the lag is visible as the last sync time metric.

The read path is the same in reverse, minus the staging: authenticate, route, read from the stream layer, stream back. A read of an archived blob fails outright until rehydrated — Archive isn't a slow read, it's not a read at all.

Control plane vs. data plane

Azure's split here is sharper than AWS's and catches literally everyone once.

[Image Prompt: 2D minimalistic split diagram of control plane and data plane for Azure Blob Storage, with Azure Resource Manager and management.azure.com governing the storage account resource on one side and the blob.core.windows.net endpoint governing blobs on the other, showing separate RBAC role sets on each side, flat design, clean vector art style, white background]

Control plane Data plane
Endpoint management.azure.com (ARM) <account>.blob.core.windows.net
Governs The account resource: create, delete, SKU, firewall, redundancy, encryption, keys, containers-as-ARM-resources The blobs: read, write, delete, list, tier, lease
Typical roles Owner, Contributor, Storage Account Contributor, Reader Storage Blob Data Reader/Contributor/Owner, Storage Blob Delegator
Auth Entra bearer token, always Entra bearer token, or account key, or SAS
Logged in Activity log Storage diagnostic logs — off by default
Throttling ARM request limits per subscription Storage scale targets per account

The classic mistake: an engineer is Owner on the subscription, opens the container in the portal, and can see the blobs — so they conclude Owner grants data access. It doesn't. The portal defaults to authenticating with the account key, which Owner is allowed to read via the control plane. Switch the portal's authentication method to "Microsoft Entra user account" and the same Owner gets AuthorizationPermissionMismatch.

Two practical consequences:

  • A Contributor is effectively a data owner as long as shared-key access is enabled, because they can list the keys. Setting allowSharedKeyAccess = false is what actually separates the two planes — and it is the single highest-value security setting on the account.
  • Terraform straddles both. azurerm_storage_account is control plane; azurerm_storage_container and azurerm_storage_blob are data plane operations, which means your pipeline's identity needs a data role and network access to the endpoint. A Terraform run that creates the account fine but fails creating the container is almost always this, or the firewall blocking the runner's IP. (The newer azurerm_storage_container behaviour and the ARM-based Microsoft.Storage/storageAccounts/blobServices/containers type both exist; check which your provider version uses ⚠️ verify against the current provider docs.)

Consistency and durability

  • Strong consistency, always. A successful write is immediately visible to every subsequent read and list, within the primary region. There is no eventual-consistency window to design around — unlike the S3 folklore you may be carrying.
  • Atomic per blob. Put Blob and Put Block List either fully succeed or don't happen. There is no cross-blob transaction, and no atomic multi-blob rename (unless HNS is on, where directory rename is atomic).
  • Three synchronous replicas within the primary location, before any write is acknowledged. That's the durability floor and it's the same for every redundancy option.
  • Geo-replication is asynchronous. RPO is greater than zero and is observable via the last-sync-time metric. Anything written after the last sync is lost in an unplanned failover.
  • Concurrency control is optimistic and opt-in. Every blob has an ETag; pass If-Match on write to get compare-and-swap semantics. Without it, last writer wins, silently. For coarser mutual exclusion, take a lease — which is exactly how the Terraform azurerm backend prevents two applies at once, and why Azure needs no equivalent of AWS's DynamoDB lock table.

Scaling model, and where the ceilings are

The single most important sentence: the scale targets are per storage account, not per container.

Limit Counted at Notes
Ingress / egress bandwidth Per storage account, per region The number varies by region, redundancy, and whether the account is GPv2 or premium ⚠️ verify current values against current Azure docs
Request rate (IOPS) Per storage account Standard accounts have a maximum request rate; premium block blob is substantially higher
Throughput per single blob/partition Per blob One blob has its own ceiling far below the account's — a single hot file cannot absorb the account's full bandwidth
Storage accounts per subscription per region Per subscription, per region A soft limit, raisable by support request ⚠️ verify
Max blob size Per blob Block blob = block size × block count; the practical ceiling is in the tens of terabytes ⚠️ verify
Containers per account, blobs per container Effectively unbounded Constrained by capacity, not by count

How to actually get more throughput, in order of preference:

  1. Spread keys across the partition space — avoid a monotonic prefix on hot write paths.
  2. Parallelise across blobs, not within one — many concurrent objects beat one giant one.
  3. Split across accounts. This is the real answer at scale and the one people resist. If one workload needs more than an account provides, shard it across accounts by tenant, dataset, or environment.
  4. Move to Premium block blob if the problem is request rate and latency rather than raw bandwidth.
  5. Put a CDN or Front Door in front for read-heavy public content — the cheapest throughput is the request that never reaches storage.

Failure modes

Throttling — 503 ServerBusy and 500 OperationTimedOut. Azure Storage signals overload primarily with 503 ServerBusy (and sometimes 500 OperationTimedOut), not the 429 you see from ARM and most other Azure services. Both are retryable with exponential backoff, and every official SDK does that by default — which is precisely why throttling often shows up as unexplained latency rather than errors. Causes: exceeding the account's request rate or bandwidth, a hot partition, or a single blob being hammered. Diagnose with the Transactions metric split by response type, not with application logs.

403 AuthorizationPermissionMismatch. Entra auth succeeded, RBAC said no. Check the data-plane role and its scope. If the role was just assigned, wait — propagation is not instant.

403 AuthorizationFailure with correct roles. Almost always the network layer: the account firewall, publicNetworkAccess = Disabled, a missing private endpoint DNS record, or the caller's IP not being in the allow list. RBAC and firewall failures look alike from the outside; the storage diagnostic logs distinguish them, which is one more reason to turn them on before you need them.

409 BlobAlreadyExists / lease conflicts. Something holds a lease — commonly a stuck Terraform apply. az storage blob lease break is the escape hatch, and using it carelessly is how two applies corrupt one state file.

409 on account creation, "name already taken". The name is globally unique across every Azure tenant, and a soft-deleted account may still be holding it.

Archive rehydration latency. Hours. Standard priority is slower than high priority; neither is interactive. Any synchronous request path touching Archive is a design bug.

Regional outage. LRS and ZRS have no cross-region copy — you wait. GRS/GZRS give you a customer-managed failover with non-zero RPO. RA-GRS/RA-GZRS let you read the secondary immediately, which for many read-heavy systems is the actual DR answer: serve stale reads rather than fail. Test the failover; the first time should not be during the incident. See Production.

Accidental deletion. Without soft delete and versioning, an overwrite is permanent. This is not a platform failure but it is the most common data-loss cause in the topic, so treat those two settings as part of the architecture rather than as optional extras.


Next: Getting Started →

← Back to the Blob Storage overview · ← Previous: Core Concepts