Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-05-18 - N+1 Query Bottlenecks in Drizzle Object Mappings
**Learning:** Found an N+1 performance bottleneck when fetching relational data manually without using Drizzle's relational query API (`db.query.*`). `getUserOrders` was performing a loop to make sequential DB requests for items belonging to each order.
**Action:** Use `inArray` to batch queries across multiple parent entity IDs to resolve N+1 scaling bottlenecks when retrieving relational data sequentially. Memory-mapping the results is O(N) compared to multiple network I/O calls.
40 changes: 26 additions & 14 deletions lib/actions/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { db } from "@/lib/db";
import { orders, orderItems, products, productVariants, user, bargainSessions } from "@/lib/db/schema";
import { getServerSession } from "@/lib/auth-server";
import { eq, desc, sql, and, isNull } from "drizzle-orm";
import { eq, desc, sql, and, isNull, inArray } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { markCouponUsed } from "./admin";

Expand Down Expand Up @@ -297,20 +297,32 @@ export async function getUserOrders() {
.where(eq(orders.userId, session.user.id))
.orderBy(desc(orders.createdAt));

// Fetch items for each order
const ordersWithItems = await Promise.all(
userOrders.map(async (order) => {
const items = await db
.select()
.from(orderItems)
.where(eq(orderItems.orderId, order.id));
if (userOrders.length === 0) {
return [];
}

return {
...order,
items,
};
})
);
// âš¡ Bolt: N+1 Query Optimization
// Replaced individual queries per order with a single batched query using inArray
// Impact: Reduces DB calls from O(N) to O(1) where N is number of orders
const orderIds = userOrders.map((order) => order.id);
const allOrderItems = await db
.select()
.from(orderItems)
.where(inArray(orderItems.orderId, orderIds));

// Group items by order ID in memory
const itemsByOrderId = allOrderItems.reduce((acc, item) => {
if (!acc[item.orderId]) {
acc[item.orderId] = [];
}
acc[item.orderId].push(item);
return acc;
}, {} as Record<string, typeof allOrderItems>);

const ordersWithItems = userOrders.map((order) => ({
...order,
items: itemsByOrderId[order.id] || [],
}));

return ordersWithItems;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/bargain-discount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export function formatBargainDiscountLabel(maxBargainDiscount: string | number |
? value.toLocaleString("en-IN")
: value.toLocaleString("en-IN", { maximumFractionDigits: 2 });

return `Bargain upto ₹${formatted}`;
return `Bargain up to ₹${formatted}`;
}