Azure Storage — Deep
Companion to the Azure Storage topic. Read that first for the basics — this file dives into blob types, tiers, lifecycle policies, SAS variants, redundancy nuances, and the legacy services (Queue, Table) you'll meet in old codebases.
Key Points
- Blob types matter for performance: Block (general), Append (logs/audit), Page (VHD/random IO). Picking wrong forces full-blob rewrites.
- Access tiers: Hot, Cool, Cold (newer, ~50% cheaper than Cool, longer min retention), Archive (offline, hours-to-rehydrate). Tier choice + lifecycle = the #1 storage cost lever.
- SAS hierarchy: account-SAS (broad, signed by key) < service-SAS (scoped, signed by key) < user-delegation SAS (Entra-issued, revocable, auditable — preferred).
- Redundancy variants: LRS, ZRS, GRS, RA-GRS, GZRS, RA-GZRS — name the trade-offs from heart.
- ADLS Gen2 = Storage account with Hierarchical Namespace + POSIX ACLs; the modern data-lake path.
Concepts (deep dive)
This file goes one layer below the Azure Storage overview into the parts that have actual behavior — not just an API surface but a data structure and a billing curve that determine whether your design works in production. Five themes shape every storage architecture: which blob type encodes the data, which tier holds it now and which tier holds it next, how access is granted (account key vs SAS vs Entra), how data survives failure (LRS → GZRS), and whether you're on plain Blob or the data-lake Gen2 namespace. Everything below is detail under those.
Blob types — the deeper picture
┌────────────────┬───────────────────────────────────────────┐
│ Block Blob │ Composed of blocks (4 KiB – 4000 MiB). │
│ │ Block IDs uploaded in parallel, then │
│ │ committed atomically (Put Block List). │
│ │ Ideal: media, backups, app data. │
├────────────────┼───────────────────────────────────────────┤
│ Append Blob │ Append-only blocks. No update; no delete │
│ │ of individual blocks. Atomic appends. │
│ │ Ideal: log streams, audit trails, IoT. │
├────────────────┼───────────────────────────────────────────┤
│ Page Blob │ 512-byte aligned random-write pages. │
│ │ Up to 8 TiB. The format underneath │
│ │ Azure managed disks / unmanaged VHDs. │
└────────────────┴───────────────────────────────────────────┘
- Cannot change blob type after creation. Recreate to switch.
- For applications: always Block unless you specifically need Append or Page semantics.
- Append blob trick: high-throughput log ingestion writes to many append blobs in parallel; one consumer reads with seek.
Access tiers — the full matrix
| Tier | Min storage duration | Storage $/GB | Read $/GB | Latency | Use |
|---|---|---|---|---|---|
| Hot | none | High | Low | ms | Active data |
| Cool | 30 days | Mid | Mid | ms | Backup snapshots, occasionally accessed |
| Cold | 90 days | Low | Higher | ms | Tier-2 backups, infrequent access |
| Archive | 180 days | Lowest | Highest + rehydrate | hours | Long-term retention, compliance |
⚠️ Early deletion fee: deleting/overwriting/tiering before min duration charges remaining days. Cool tier deleted on day 5 → pay 25 days early-delete penalty.
💡 Cold vs Cool: Cold (announced 2023, GA 2024) is ~50% cheaper than Cool but with 90-day min retention vs 30. Better fit for long-tail data not yet ready for Archive.
Lifecycle management policies
{
"rules": [{
"name": "TieringAndExpiry",
"enabled": true,
"type": "Lifecycle",
"definition": {
"filters": {
"blobTypes": ["blockBlob"],
"prefixMatch": ["logs/"]
},
"actions": {
"baseBlob": {
"tierToCool": { "daysAfterModificationGreaterThan": 30 },
"tierToCold": { "daysAfterModificationGreaterThan": 90 },
"tierToArchive": { "daysAfterLastAccessTimeGreaterThan": 180 },
"delete": { "daysAfterModificationGreaterThan": 2555 }
},
"snapshot": { "delete": { "daysAfterCreationGreaterThan": 30 } },
"version": { "delete": { "daysAfterCreationGreaterThan": 90 } }
}
}
}]
}
- Rules run once per day.
daysAfterLastAccessTimeGreaterThanrequires last-access-time tracking enabled (small write-amp cost).- Independent expiry for snapshots and versions — without these, versioning costs explode.
Immutable blob storage (WORM)
Two policy types:
Time-based retention Legal hold
───────────────────── ──────────────────────
Lock until date Lock until tag removed
e.g. retain 7 years Remains until cleared
SEC 17a-4 compliance Litigation hold
await container.SetImmutabilityPolicyAsync(
new BlobImmutabilityPolicy
{
ExpiresOn = DateTimeOffset.UtcNow.AddYears(7),
PolicyMode = BlobImmutabilityPolicyMode.Locked
});
Locked policies cannot be shortened (only extended). Once locked = compliance-grade.
Versioning, snapshots, soft delete — three different things
| What | Triggered by | Cost impact | |
|---|---|---|---|
| Versioning | Auto-creates a version on every modification | Always-on or off | 🔴 Easily 5–10× storage if write-heavy |
| Snapshot | Manual point-in-time copy | Explicit API call | Predictable |
| Blob soft delete | Tombstone for N days after delete | Always-on (configurable retention) | Modest |
| Container soft delete | Restore deleted container | Always-on | Modest |
✅ Always enable soft delete on prod accounts (7–30 days). Restore from accidental deletes is a save-your-job feature. ⚠️ Versioning without lifecycle expiry on versions = unbounded growth. Pair them.
SAS — three flavors
Account SAS Service SAS User Delegation SAS
────────── ────────── ───────────────────
Signed by Account key Account key Entra ID (AAD)
Scope Account-wide Container/blob Container/blob
Audit Limited Limited Full Entra audit
Revocation Rotate key Stored access pol. Revoke Entra creds
Cross-tenant ✅ ✅ ✅
Preferred? ❌ (broad) ⚠️ (key-based) ✅
// User-delegation SAS (preferred)
var udk = await blobService.GetUserDelegationKeyAsync(
DateTimeOffset.UtcNow.AddMinutes(-1),
DateTimeOffset.UtcNow.AddMinutes(15));
var sasBuilder = new BlobSasBuilder
{
BlobContainerName = "uploads",
BlobName = "user-1/photo.png",
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(15),
Resource = "b"
};
sasBuilder.SetPermissions(BlobSasPermissions.Read);
var sasToken = sasBuilder.ToSasQueryParameters(
udk, blobService.AccountName).ToString();
Stored access policy (for service-SAS revocation)
await container.SetAccessPolicyAsync(
permissions: new[]
{
new BlobSignedIdentifier
{
Id = "policy1",
AccessPolicy = new BlobAccessPolicy
{
ExpiresOn = DateTimeOffset.UtcNow.AddDays(7),
Permissions = "r"
}
}
});
SAS tokens reference the policy ID; deleting the policy revokes all SAS tokens that depended on it.
Static website hosting
az storage blob service-properties update \
--account-name myacct --static-website \
--index-document index.html --404-document 404.html
Serves SPA from $web container at https://myacct.z13.web.core.windows.net. Pair with Front Door (custom domain, TLS, WAF, CDN) for production.
Azure Data Lake Gen2
A Storage account with Hierarchical Namespace (HNS) enabled at creation:
- Real directories (atomic rename
/raw/→/processed/). - POSIX ACLs (per directory, per file, default ACLs inheriting).
- Same blob APIs work; plus the DFS (
dfs.core.windows.net) endpoint for HDFS-compatible access. - Used by Synapse, Databricks, Fabric, HDInsight.
⚠️ Cannot enable HNS on existing storage account; must be at creation.
AzCopy + Storage Mover
- AzCopy v10: parallel, resumable, the de-facto bulk copy CLI.
- Storage Mover: managed agent for on-prem-to-Azure migrations at scale (multiple TB).
- Use
--block-size-mbto tune large blobs; default 8 MiB.
Storage account types
| Type | Use |
|---|---|
| Standard general-purpose v2 (GPv2) | Default — Blob/Queue/Table/File together |
| Premium block blob | Low-latency Blob (sub-10ms p95) |
| Premium file shares (FileStorage) | High-perf SMB / NFS |
| Premium page blob (BlockBlobStorage variant) | High-IOPS unmanaged disks |
| BlobStorage (legacy) | Older; superseded by GPv2 |
GPv2 covers 95% of cases. Premium when latency/IOPS demands.
Redundancy — full breakdown
LRS : 3 copies, 1 zone, 1 region
ZRS : 3 copies, 3 zones, 1 region
GRS : LRS + async LRS in paired region
GZRS : ZRS + async LRS in paired region
RA-GRS : GRS + read endpoint to secondary
RA-GZRS: GZRS + read endpoint to secondary
| Zone failure | Region failure | Read secondary | |
|---|---|---|---|
| LRS | ❌ | ❌ | ❌ |
| ZRS | ✅ | ❌ | ❌ |
| GRS | ❌ (one zone) | ✅ (manual failover) | ❌ |
| RA-GRS | ❌ | ✅ | ✅ (eventually consistent) |
| GZRS | ✅ | ✅ | ❌ |
| RA-GZRS | ✅ | ✅ | ✅ |
💡 GRS / GZRS replication is async — RPO is seconds-to-minutes; expect data loss on unplanned failover.
Queue Storage — the legacy
- Up to 64 KiB per message, max 7-day retention.
- No sessions, no dead-letter, no transactions, no topics.
- Service Bus is preferred for new code; Queue Storage exists for trivial fan-out and very cheap workloads.
Table Storage — the legacy NoSQL
var table = new TableClient(connStr, "Logs");
await table.AddEntityAsync(new TableEntity("2026-04", "id-001") { ["Msg"] = "x" });
- PartitionKey + RowKey, no indexing options.
- Cheap for high-volume telemetry.
- Cosmos Table API is the modern replacement: same data model + global distribution, multi-region writes, predictable latency, SLAs.
- Migrate when feature needs grow (queries, throughput SLAs).
Azure Files
| Protocol | Auth | Use |
|---|---|---|
| SMB 3.x | AAD/Kerberos, account key | Windows file share replacement |
| NFS 4.1 | Network ACL only (mount IP allowlist) | Linux workloads, container persistent volumes |
- AAD-joined SMB (Hybrid identities): Azure Files supports identity-based auth using AD DS or Microsoft Entra Domain Services.
- Premium tier for low-latency / high-IOPS workloads (databases on NFS shares, etc.).
- Azure File Sync: cache cloud share on-prem with tiering.
Network access for Storage
- Public (default): firewall allowlist of IPs / VNets via Service Endpoints.
- Service Endpoint: optimized route from VNet; resource keeps public DNS.
- Private Endpoint: real private NIC inside your subnet; resolution via
privatelink.blob.core.windows.netPrivate DNS Zone.
For production: Private Endpoint + firewall public access disabled.
Encryption
- At rest: always on; default Microsoft-managed keys (MMK).
- Customer-Managed Keys (CMK) via Key Vault for compliance / BYOK; supports key rotation.
- Customer-Provided Keys (CPK): per-request key on Blob; rare.
- Double encryption (infrastructure encryption) for highest tier security needs.
- In transit: HTTPS-only; SMB 3.x with encryption.
.NET SDK essentials
// Bulk parallel upload
var transfer = new BlobUploadOptions
{
TransferOptions = new StorageTransferOptions
{
InitialTransferSize = 8 * 1024 * 1024,
MaximumTransferSize = 16 * 1024 * 1024,
MaximumConcurrency = 16
}
};
await blob.UploadAsync(stream, transfer);
// Resilience
var clientOptions = new BlobClientOptions
{
Retry =
{
MaxRetries = 5,
Mode = RetryMode.Exponential,
NetworkTimeout = TimeSpan.FromSeconds(30)
}
};
How it works under the hood
- Storage accounts use stamps (clusters of nodes) within a region. Each stamp has front-end nodes, partition layer, and stream layer.
- Blob writes commit to the stream layer (replicated 3x within the stamp for LRS) before ack.
- ZRS spreads three copies across three zones in real time (sync) — true zone resilience.
- GRS replicates the entire account asynchronously to the paired region's stamp.
- Last-access tracking is implemented as a periodic background scan (not strictly real-time) to keep cost down.
- Lifecycle policies evaluate once per day; large accounts may take hours to fully process.
- Soft delete moves blob into a tombstoned state; restoration unsets the flag.
Code: correct vs wrong
❌ Wrong: enabling versioning without lifecycle
await blobService.SetServicePropertiesAsync(new BlobServiceProperties
{
IsVersioningEnabled = true
});
// No version-expiry policy → unbounded version retention
✅ Correct: versioning + lifecycle expiry
❌ Wrong: account SAS with full perms forever
var sas = new AccountSasBuilder(
permissions: AccountSasPermissions.All,
expiresOn: DateTimeOffset.MaxValue,
services: AccountSasServices.All,
resourceTypes: AccountSasResourceTypes.All);
✅ Correct: scoped, short, user-delegation SAS
var sas = new BlobSasBuilder
{
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(15),
BlobContainerName = "uploads",
BlobName = "user-1/photo.png",
Resource = "b"
};
sas.SetPermissions(BlobSasPermissions.Read);
// signed with user-delegation key
❌ Wrong: Hot tier as default for everything
Backups, audit logs, year-end reports — all sitting in Hot.
✅ Correct: lifecycle to Cool/Cold/Archive
Move to Cool after 30 days, Archive after 180.
Design patterns for this topic
Pattern 1 — "Tier + lifecycle for retention"
- Intent: automatic cost optimization without human discipline.
Pattern 2 — "User-delegation SAS for shareable links"
- Intent: time-limited access with full Entra audit trail and revocation.
Pattern 3 — "Direct browser upload via SAS"
- Intent: offload bandwidth from app server; client uploads directly to blob.
Pattern 4 — "Immutable WORM for compliance data"
- Intent: SEC 17a-4, HIPAA, legal-hold immutability.
Pattern 5 — "ADLS Gen2 for analytics workloads"
- Intent: real directories + POSIX ACLs for Synapse / Databricks / Fabric.
Pros & cons / trade-offs
| Aspect | Pros | Cons |
|---|---|---|
| Block Blob | General, parallel uploads | Not random-write |
| Append Blob | Cheap log ingestion | Append-only |
| Page Blob | Random IO | Niche outside disks |
| Cold tier | Cheaper than Cool | 90-day min retention |
| Archive | Cheapest | Hours to rehydrate |
| ZRS / GZRS | Zone resilient | Slightly higher cost |
| Versioning | Recovery | Cost explosion w/o lifecycle |
| ADLS Gen2 | Real dirs + ACLs | HNS only at create-time |
| Queue Storage | Cheap, simple | No sessions / DLQ — use Service Bus |
| Table Storage | Cheap NoSQL | No SLAs / global; use Cosmos Table |
When to use / when to avoid
- Use Block Blob for almost everything; Append for log streams; Page only with disks.
- Use Cold tier for tier-2 backups (>90 days, <2 years).
- Use Archive for retention beyond 2 years where minutes-to-hours retrieval is acceptable.
- Use user-delegation SAS for any shareable link; deprecate account-key SAS.
- Use ADLS Gen2 for any analytics / data-lake workload.
- Avoid Versioning without lifecycle policies on versions.
- Avoid Queue Storage / Table Storage in greenfield code; default to Service Bus / Cosmos.
- Avoid Hot tier as the global default — measure access; tier accordingly.
Interview Q&A
Q1. Block vs Append vs Page Blob? Block: general, parallel uploads. Append: append-only, logs. Page: 512-byte aligned random write, VHDs.
Q2. Tiers and minimum retention? Hot (none), Cool (30d), Cold (90d), Archive (180d). Early-delete fees if shorter.
Q3. Lifecycle policy granularity? Per-prefix, per-blob-type. Actions on base blob, snapshots, versions independently.
Q4. SAS types and which is preferred? Account, Service, User-Delegation. UD-SAS is preferred — Entra-signed, auditable, revocable.
Q5. Stored access policy? Container-level policy that SAS tokens reference; delete = revoke all dependent SAS.
Q6. Versioning vs snapshots? Versioning: auto on every change. Snapshots: explicit point-in-time. Soft delete: tombstone for N days.
Q7. WORM / immutable? Time-based or legal hold; locked policies can't shorten — compliance grade.
Q8. ADLS Gen2 vs GPv2? Gen2 = HNS + POSIX ACLs; for analytics. Must enable HNS at creation.
Q9. RA-GRS? Read-access GRS; secondary endpoint readable (eventually consistent); failover unchanged.
Q10. Why prefer Service Bus over Queue Storage? Sessions, DLQ, transactions, topics, sub auto-forwarding, larger messages.
Q11. Why prefer Cosmos Table API over Table Storage? Global distribution, multi-region writes, predictable latency, SLAs.
Q12. Encryption choices? MMK (default), CMK (BYOK via Key Vault), CPK (per-request). Plus infrastructure encryption for double.
Gotchas / common mistakes
- ⚠️ Versioning ON without lifecycle → unbounded growth.
- ⚠️ Hot tier for cold data → wasted money.
- ⚠️ Early-delete fee when moving Cool/Cold/Archive blobs prematurely.
- ⚠️ Account-key SAS leaked → can only revoke by rotating the key (which breaks every consumer).
- ⚠️ Soft delete OFF in prod → no recovery from rm -rf.
- ⚠️ HNS not enabled at create — can't add ADLS Gen2 features later.
- ⚠️ Public access default on a new container — most leaks come from this.
- ⚠️ GRS treated as synchronous — it isn't; expect data loss on failover.
- ⚠️ Treating Queue/Table Storage as feature parity with Service Bus / Cosmos.