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 @@
## 2024-05-18 - Fix N+1 Query in getUserOrders
**Learning:** Found an N+1 query vulnerability when iterating over fetched entities and dispatching separate requests to fetch child entities (fetching orderItems for each order). In serverless setups using Drizzle ORM this causes a database request amplification that degrades performance severely at scale.
**Action:** Replace `Promise.all` loops running queries with single batch requests utilizing Drizzle's `inArray` operator, then map the data back using reduce maps grouping by foreign key.
39 changes: 25 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,31 @@ 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 Optimization: Fix N+1 query problem by batch fetching all related order items
// Reduces DB queries from O(N) to O(1) where N is number of orders
const orderIds = userOrders.map((order) => order.id);
const allItems = await db
.select()
.from(orderItems)
.where(inArray(orderItems.orderId, orderIds));

// Group items by orderId to avoid nested loops mapping them
const itemsByOrderId = allItems.reduce((acc, item) => {
if (!acc[item.orderId]) {
acc[item.orderId] = [];
}
acc[item.orderId].push(item);
return acc;
}, {} as Record<string, typeof allItems>);

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}`;
}