NoSQL Primary Keys: Things to consider

NoSQL Primary Keys: Things to consider

DatabaseNoSql

Tue Nov 28 2023

For some that know me, they know i am a big NoSQL fan. I like its scalability, i like its relative ease of use and i like its performance. But that doesn't come automatically. First you must model your data appropriately, and of that, is primary key selection.

The design of primary keys is not just a mere step in database setup; it's a pivotal decision that shapes data access patterns, scalability, and overall performance. In this post, i will look at best practices for primary key design in NoSQL systems.

Understanding Access Patterns

Before you even start thinking about your primary key, take a step back and consider how your application interacts with the data. Which queries are most frequent? What kind of data are you accessing? The answers to these questions should guide your primary key design, ensuring it aligns perfectly with common access patterns.

DO:

  • Design primary keys based on frequent query patterns. For example, if you often query by date and user ID:
{ "primaryKey": { "date": "2021-07-01", "userId": "user123" } }
json

DON'T DO:

  • Avoid primary keys that don't align with your query patterns. For instance, using a random string as a primary key when you usually query by date:
{ "primaryKey": "randomString123" }
json

The Power of Composite Primary Keys

NoSQL databases, like Cassandra or DynamoDB, often employ composite primary keys. These keys consist of a partition key and a sort key. The partition key plays a crucial role in distributing data across various nodes, ensuring a balanced load and preventing hotspots. Meanwhile, the sort key comes in handy for efficiently querying ranges of data within each partition.

DO:

  • Use composite keys in DynamoDB for efficient querying:
{ "partitionKey": "blogPost", "sortKey": "2021-07-01#post123" }
json

DON'T DO:

  • Don't use a single attribute as a primary key when your access pattern requires querying on multiple attributes:
{ "primaryKey": "blogPost" }
json

Uniqueness and Scalability: A Balancing Act

A primary key in a NoSQL database must be unique; otherwise, you risk data overwrites or loss. But that's not all – your primary key must also be scalable. Avoid designs that could lead to skewed data distribution across nodes, which can bottleneck your system.

DO:

  • Ensure primary keys are unique and scalable. For instance, combining user ID and timestamp in a log data model:
{ "primaryKey": "user123#1625157600" }
json

DON'T DO:

  • Avoid non-scalable primary keys, like using only a username in a large user base:
{ "primaryKey": "user123" }
json

Size Matters: Keep Primary Keys Concise

Bigger isn't always better, especially when it comes to primary keys. Oversized keys take up more space and can degrade performance. Aim for a balance between simplicity, uniqueness, and adherence to your data access patterns.

DO:

  • Use concise but unique identifiers, like short UUIDs:
{ "primaryKey": "a1b2c3" }
json

DON'T DO:

  • Avoid overly long or complex keys:

{ "primaryKey": "a1b2c3d4-e5f6-g7h8-i9j0-k11l12m13n14" }
json

Natural vs. Synthetic Keys: A Strategic Choice

Deciding between natural keys (derived from data like an email address) and synthetic keys (like UUIDs) can be tricky. Natural keys are intuitive but can change, posing a challenge. Synthetic keys, while not insightful, guarantee uniqueness.

DO:

  • Use synthetic keys for stable, unique identifiers:
{ "primaryKey": "uuid-1234-abcd" }
json

DON'T DO:

  • Avoid using changeable natural keys, like email addresses, as primary keys:
{ "primaryKey": "user@email.com" }
json

Tread Carefully with Sequential Keys

In scenarios with high write volumes, sequential keys (think timestamps or auto-incrementing IDs) can become a liability, leading to write operation hotspots. This is a critical consideration for maintaining performance in high-throughput environments.

DO:

  • Use non-sequential keys for high-throughput scenarios:
{ "primaryKey": "uuid-1234-abcd" }
json

DON'T DO:

  • Avoid sequential keys in write-heavy applications:
{ "primaryKey": "20210701-0001" }
json

Navigating Relationships and Joins

NoSQL databases typically don't excel at handling joins. Design your primary keys to minimize the need for cross-table joins, which can complicate queries and affect performance.

DO:

  • Optimize primary keys to avoid the need for joins:
{ "userPrimaryKey": "user123", "orderPrimaryKey": "order456" }
json

DON'T DO:

  • Don't create primary keys that frequently require cross-table joins:
{ "foreignKey": "user123" }
json

Embracing Data Modeling Techniques

Depending on your NoSQL database type and access patterns, you might need to employ techniques like denormalization, aggregation, or data duplication. These strategies can optimize data retrieval and enhance performance.

DO:

  • Use techniques like aggregation for efficient data access:
{ "userId": "user123", "aggregateData": { "orders": 5, "totalSpent": 200 } }
json

DON'T DO:

  • Avoid overly normalized models that require multiple database hits for common queries:
{ "userId": "user123", "orderId": "order456" }
json

Each NoSQL database, be it DynamoDB, Cassandra, or MongoDB, has its unique characteristics and requirements. My experience primarily comes from DDB, but have used the others as well. The art of designing primary keys lies in understanding these nuances and tailoring your approach to suit the specific database and your application's needs. Remember, the ultimate goal is to create primary keys that foster efficient data retrieval and maintain high performance as your system scales, hence you are leveraging many benefits of going NoSQL in the first place.