
Understanding the N+1 Problem and BatchGetItem in DynamoDB vs SQL
When working with databases, one common performance issue is the N+1 problem. This occurs when you execute one query to fetch a list of items and then perform N additional queries to retrieve related data for each item. While relational databases (like SQL) and DynamoDB both face this challenge, the way you handle it differs significantly.

AWS RDS vs DynamoDB: Different approaches to solving the N+1 problem
What is the N+1 Problem?
The N+1 problem happens when your application runs:
- 1 query to get a list of users.
- N queries to get related data (e.g., orders for each user).
Example in SQL:
SELECT * FROM users; -- First query
-- Then, for each user:
SELECT * FROM orders WHERE user_id = ?; -- N queries
If you have 100 users, that means 101 queries. This leads to high latency and unnecessary database load.
How SQL Solves N+1
Relational databases provide a solution using JOINs. You can fetch all users and their orders in a single query:
SELECT users.id, users.name, orders.id AS order_id, orders.total
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
This reduces the complexity from O(N)
queries to O(1)
.
The Challenge in DynamoDB
DynamoDB is a NoSQL database optimized for speed and scalability. Unlike SQL, it does not support JOINs or dynamic filtering like WHERE user_id IN (...)
. Queries are limited to:
- Primary Key (PK) lookups.
- Optional range conditions on Sort Keys (SK).
- Secondary indexes (GSI/LSI) are defined beforehand.
This means you cannot write a single query to fetch multiple users and all their orders unless you design your table specifically for that access pattern.
Naive Approach in DynamoDB (N+1 Issue)
# First query: list of users
users = table.scan(FilterExpression=Attr('type').eq('user'))
# Then, for each user, get their orders
for user in users['Items']:
orders = table.query(
KeyConditionExpression=Key('PK').eq(f"USER#{user['id']}")
& Key('SK').begins_with('ORDER#')
)
This is 1 + N queries, the classic N+1 problem.
Optimizing with BatchGetItem
DynamoDB provides the BatchGetItem
operation, which lets you fetch up to 100 items per request when you know their exact keys. This works like an IN clause in SQL, but only for primary keys.
Example with BatchGetItem:
import boto3
dynamodb = boto3.client('dynamodb')
keys = [
{'PK': {'S': 'USER#123'}, 'SK': {'S': 'PROFILE'}},
{'PK': {'S': 'USER#456'}, 'SK': {'S': 'PROFILE'}}
]
response = dynamodb.batch_get_item(
RequestItems={
'MyTable': {'Keys': keys}
}
)
However, unlike SQL WHERE user_id IN (...)
, you cannot filter by arbitrary attributes. BatchGetItem requires the exact PK (and SK if present).
- SQL can query with dynamic conditions and join related tables in one query.
- DynamoDB cannot perform joins; you must design your data model to minimize queries (e.g., Single Table Design).
BatchGetItem
helps reduce multiple requests but is limited to known keys and max 100 items per call.
Technical Comparison: SQL vs DynamoDB
- SQL: Flexible queries, supports JOIN, WHERE, GROUP BY. Handles N+1 with JOIN.
- DynamoDB: Fixed access patterns, requires denormalization or parallel queries. Handles N+1 with BatchGetItem or Single Table Design.
Conclusion
The N+1 problem affects both SQL and NoSQL systems, but the solutions differ. In SQL, JOIN solves it elegantly. In DynamoDB, you must either pre-design your schema for single-query patterns or use BatchGetItem when you know the exact keys. The right choice depends on scalability needs and query flexibility requirements.