diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..0d2a656 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-25 - Parallelize Independent Sequential Drizzle Queries +**Learning:** Found multiple instances where independent database queries (like paginated fetches alongside total count queries, or multiple aggregate queries for a dashboard) were being awaited sequentially using `await db.select(...)`. In this Next.js Drizzle architecture, this creates an unnecessary waterfall of requests, increasing latency. +**Action:** When interacting with the database via Drizzle ORM, parallelize independent sequential queries using `Promise.all([db.select(...), db.select(...)])` to minimize overall request latency. diff --git a/app/api/products/route.ts b/app/api/products/route.ts index 953d709..1072c86 100644 --- a/app/api/products/route.ts +++ b/app/api/products/route.ts @@ -58,19 +58,20 @@ export async function GET(req: NextRequest) { conditions.push(eq(products.isFeatured, true)); } - const result = await db - .select() - .from(products) - .where(and(...conditions)) - .orderBy(desc(products.displayOrder), desc(products.createdAt)) - .limit(limit) - .offset(offset); - - // Get total count for pagination - const [{ count }] = await db - .select({ count: sql`count(*)` }) - .from(products) - .where(and(...conditions)); + // Parallelize data fetch and total count queries to reduce latency + const [result, [{ count }]] = await Promise.all([ + db + .select() + .from(products) + .where(and(...conditions)) + .orderBy(desc(products.displayOrder), desc(products.createdAt)) + .limit(limit) + .offset(offset), + db + .select({ count: sql`count(*)` }) + .from(products) + .where(and(...conditions)) + ]); return NextResponse.json({ products: result, diff --git a/lib/actions/admin.ts b/lib/actions/admin.ts index 54c3848..75fd5b5 100644 --- a/lib/actions/admin.ts +++ b/lib/actions/admin.ts @@ -506,34 +506,35 @@ export async function getDashboardStats() { const now = new Date(); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); - // Total products - const [{ count: totalProducts }] = await db - .select({ count: sql`count(*)` }) - .from(products) - .where(eq(products.isActive, true)); - - // Total orders (last 30 days) - const [{ count: totalOrders }] = await db - .select({ count: sql`count(*)` }) - .from(orders) - .where(gte(orders.createdAt, thirtyDaysAgo)); - - // Total revenue (last 30 days) - const [{ sum: totalRevenue }] = await db - .select({ sum: sql`COALESCE(sum(total), 0)` }) - .from(orders) - .where( - and( - gte(orders.createdAt, thirtyDaysAgo), - eq(orders.status, "delivered") - ) - ); - - // Active coupons - const [{ count: activeCoupons }] = await db - .select({ count: sql`count(*)` }) - .from(coupons) - .where(eq(coupons.isActive, true)); + // Run independent sequential queries concurrently to avoid waterfall requests + const [ + [{ count: totalProducts }], + [{ count: totalOrders }], + [{ sum: totalRevenue }], + [{ count: activeCoupons }] + ] = await Promise.all([ + db + .select({ count: sql`count(*)` }) + .from(products) + .where(eq(products.isActive, true)), + db + .select({ count: sql`count(*)` }) + .from(orders) + .where(gte(orders.createdAt, thirtyDaysAgo)), + db + .select({ sum: sql`COALESCE(sum(total), 0)` }) + .from(orders) + .where( + and( + gte(orders.createdAt, thirtyDaysAgo), + eq(orders.status, "delivered") + ) + ), + db + .select({ count: sql`count(*)` }) + .from(coupons) + .where(eq(coupons.isActive, true)) + ]); return { totalProducts: Number(totalProducts),