Skip to content

Commit 0888650

Browse files
feat(laravel): mimick behavior of flask: doing heavy computation to slow down response on purpose and introduce N+1 query (#1161)
* feat(laravel): make the product query an N+1 query * feat(laravel): mimick behavior of flask: doing heavy computation to slow down response on purpose
1 parent 4c15681 commit 0888650

File tree

1 file changed

+48
-6
lines changed

1 file changed

+48
-6
lines changed

laravel/app/Http/Controllers/Api/ProductController.php

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414

1515
class ProductController extends Controller
1616
{
17+
private const PESTS = [
18+
"aphids", "thrips", "spider mites", "lead miners", "scale",
19+
"whiteflies", "earwigs", "cutworms", "mealybugs", "fungus gnats"
20+
];
21+
22+
private const NORMAL_SLOW_PROFILE = 2; // seconds
23+
private const EXTREMELY_SLOW_PROFILE = 24;
24+
1725
public function __construct(
1826
private OrderService $orderService
1927
) {}
@@ -22,8 +30,13 @@ public function __construct(
2230
* Get all products with reviews
2331
* Extracted from the original /products route
2432
*/
25-
public function index(): JsonResponse
33+
public function index(Request $request): JsonResponse
2634
{
35+
$fetchPromotions = $request->query('fetch_promotions');
36+
$inStockOnly = $request->query('in_stock_only');
37+
$runSlowProfile = env('RUN_SLOW_PROFILE', true);
38+
$timeoutSeconds = $fetchPromotions ? self::EXTREMELY_SLOW_PROFILE : self::NORMAL_SLOW_PROFILE;
39+
2740
// Generate random number to determine cache key strategy
2841
$randomValue = rand(0, 99);
2942

@@ -43,12 +56,16 @@ public function index(): JsonResponse
4356
}
4457

4558
// Cache miss - fetch from database
46-
$products = Product::with('reviews')->get();
47-
59+
// N+1 query: first get all products, then query reviews for each product individually
60+
$products = Product::all();
61+
4862
// Transform to match original format
63+
// This causes N+1 queries - for each product, a separate query is made to fetch reviews
4964
$completeProductsWithReviews = $products->map(function ($product) {
5065
$productArray = $product->toArray();
51-
$productArray['reviews'] = $product->reviews->map(function ($review) {
66+
// Accessing $product->reviews triggers a lazy load query for each product
67+
$reviews = DB::table('reviews')->where('productid', $product->id)->get();
68+
$productArray['reviews'] = $reviews->map(function ($review) {
5269
return [
5370
'id' => $review->id,
5471
'productid' => $review->productid,
@@ -58,10 +75,35 @@ public function index(): JsonResponse
5875
'created' => $review->created,
5976
];
6077
})->toArray();
61-
78+
6279
return $productArray;
6380
})->toArray();
64-
81+
82+
// Simulate computation-heavy work when RUN_SLOW_PROFILE is enabled
83+
if ($runSlowProfile) {
84+
$startTime = microtime(true);
85+
$descriptions = array_column($completeProductsWithReviews, 'description');
86+
$loop = count($descriptions) * 6 + ($fetchPromotions ? 2 : -1);
87+
88+
for ($i = 0; $i < $loop * 10; $i++) {
89+
$timeDelta = microtime(true) - $startTime;
90+
if ($timeDelta > $timeoutSeconds) {
91+
break;
92+
}
93+
94+
foreach ($descriptions as $index => $description) {
95+
foreach (self::PESTS as $pest) {
96+
if ($inStockOnly && !isset($completeProductsWithReviews[$index])) {
97+
continue;
98+
}
99+
if (str_contains($description ?? '', $pest)) {
100+
array_splice($completeProductsWithReviews, $index, 1);
101+
}
102+
}
103+
}
104+
}
105+
}
106+
65107
// Store in cache (1 hour TTL for fixed key, random keys will never be retrieved anyway)
66108
Cache::put($cacheKey, $completeProductsWithReviews, 3600);
67109

0 commit comments

Comments
 (0)