Optimal PartitionKey & RowKey Design for Blog Posts

Following on from a previous article looking into what is good practice for defining PartitionKey and RowKey for Azure Table storage, here we take a real life scenario and look at some of the possible values we could use for when storing a blog post.

🎯 Your main query patterns will likely be:

  • Get a single post by slug or ID
  • List posts by publish date
  • List posts by category or tag
  • List posts by author
  • Show the latest N posts

Azure Tables don’t support secondary indexes, so you design your keys around these patterns.


✔️ Recommended Primary Table: Posts

PartitionKey: yyyy-MM (publish month)

RowKey: slug or inverted timestamp

This gives you:

  • Fast “latest posts” queries
  • Natural grouping by month
  • Even distribution (no hotspots)
  • Easy retrieval of a single post

Example

PartitionKey RowKey Title
2025-02 my-first-post My First Post
2025-02 MaxTicks-638742... Another Post
2025-03 building-a-cms Building a CMS

Why this works

  • Blog posts are naturally time‑series data.
  • Monthly partitions keep partitions from getting too large.
  • You can fetch the latest posts by querying the most recent partitions.

✔️ Alternative RowKey Options

Option A: Slug (human‑friendly)

  • Easy to retrieve a post directly
  • Great for SEO‑style URLs
  • Good if slugs are unique

Option B: Inverted timestamp (machine‑friendly)

  • Perfect for “latest first” queries
  • RowKey sorts newest → oldest
  • Format:
    RowKey = (DateTime.MaxValue - PublishDate).Ticks.ToString("d19")

✔️ Handling Categories, Tags, and Authors

Azure Tables don’t support secondary indexes, so you create additional tables for fast lookups.

Table: PostsByCategory

  • PartitionKey: category
  • RowKey: yyyy-MM-dd-HH-mm-ss + slug
  • Value: pointer to the main post (PartitionKey + RowKey)

Table: PostsByTag

Same pattern as categories.

Table: PostsByAuthor

Same pattern again.

These tables act like manual indexes—super cheap, super fast.


✔️ Summary of the Best Practice

Table PartitionKey RowKey Purpose
Posts yyyy-MM slug or inverted timestamp Main storage
PostsByCategory category timestamp+slug Fast category queries
PostsByTag tag timestamp+slug Fast tag queries
PostsByAuthor authorId timestamp+slug Fast author queries

This structure scales beautifully and keeps queries predictable and cheap.

Best Practices for PartitionKey and RowKey in Azure Table Storage

1. PartitionKey: Design for Scalability and Query Patterns

The PartitionKey determines how your data is distributed across storage nodes. Good partitioning avoids hotspots and keeps queries fast.

✔️ Best Practices

  • Group entities that you frequently query together
    Azure Tables can only efficiently query within a partition. If you often query “all orders for a customer,” then PartitionKey = CustomerId is a strong choice.

  • Avoid extremely large single partitions
    A single partition can only scale so far. If you expect millions of entities in one partition, consider adding a secondary dimension, such as:

    • CustomerId + Month
    • DeviceId + Date
    • Region + Category
  • Avoid extremely small partitions
    Too many tiny partitions can slow down scans and increase overhead.

  • Choose keys that evenly distribute load
    If all writes go to the same partition (e.g., PartitionKey = "Orders"), you create a hotspot. Spread writes across partitions.

✔️ Good PartitionKey examples

Scenario Good PartitionKey
IoT telemetry DeviceId or DeviceId + Date
Multi-tenant SaaS TenantId
Logging Date (e.g., 2025-02-04)
E‑commerce orders CustomerId or CustomerId + Year

2. RowKey: Ensure Uniqueness and Fast Lookup

The RowKey uniquely identifies an entity within a partition. Azure Tables sort RowKeys lexicographically.

✔️ Best Practices

  • Make RowKey unique within the partition
    Common patterns:

    • GUID
    • Timestamp (inverted for newest-first)
    • Natural key (OrderId, UserId, etc.)
  • Use RowKey to optimize query order
    Because RowKeys are sorted, you can:

    • Store newest items first using a descending timestamp trick:
      RowKey = (DateTime.MaxValue - timestamp).Ticks
    • Store items alphabetically or numerically for range queries.
  • Keep RowKeys short
    Long keys increase storage cost and slow down queries.

✔️ Good RowKey examples

Scenario Good RowKey
Logging Inverted timestamp (RowKey = MaxTicks - Now.Ticks)
Orders OrderId
IoT telemetry Timestamp or sequence number
User profiles UserId

3. General Key Design Principles

✔️ Keep keys ASCII-safe

Avoid characters that require escaping (/, \, #, ?).

✔️ Keep keys predictable

You want to be able to compute the key without extra lookups.

✔️ Keep keys immutable

Changing keys means deleting and re‑inserting the entity.

✔️ Think about your query patterns first

Azure Tables are not relational. You design keys based on how you read data, not how you model it.


4. Common Patterns (with examples)

Pattern A: Time-series data

PartitionKey: DeviceId
RowKey: Inverted timestamp

  • Fast “latest first” queries
  • Even distribution across devices

Pattern B: Multi-tenant SaaS

PartitionKey: TenantId
RowKey: EntityId

  • Easy to isolate tenant data
  • Scales well

Pattern C: Event logs

PartitionKey: Date (e.g., 2025-02-04)
RowKey: GUID or timestamp

  • Efficient daily queries
  • Avoids giant partitions

5. Anti‑Patterns (Avoid These)

❌ PartitionKey = same value for all rows

Creates a massive hotspot.

❌ RowKey = random GUID when you need sorted queries

GUIDs destroy ordering.

❌ PartitionKey = GUID

You lose the ability to query groups of related data.

❌ Too many partitions (e.g., PartitionKey = GUID per row)

Makes range scans impossible.


Summary

PartitionKey

  • Group related data
  • Spread load
  • Match your query patterns
  • Avoid hotspots

RowKey

  • Unique within partition
  • Sorted for fast range queries
  • Short and predictable

Together, they define your performance, scalability, and cost.