Skip to content
advanced Phase 108 · Code Review

Performance Review

45m
1 problems
Topic Progress 0%

Performance Review Overview

Performance Review Areas

Performance Review Checklist:
├── Database
│   ├── N+1 queries
│   ├── Missing indexes
│   ├── Full table scans
│   ├── Large result sets
│   └── Unoptimized joins
├── Caching
│   ├── Missing cache usage
│   ├── Inefficient cache keys
│   ├── Cache stampede risk
│   └── Cache invalidation
├── Code
│   ├── Inefficient algorithms
│   ├── Memory leaks
│   ├── Excessive loops
│   └── Blocking operations
└── Frontend
    ├── Render-blocking resources
    ├── Large assets
    ├── Unoptimized images
    └── Excessive DOM operations

Performance Metrics

// Key performance metrics
$metrics = [
    'response_time' => [
        'p50' => 200, // ms
        'p95' => 500,
        'p99' => 1000
    ],
    'throughput' => [
        'requests_per_second' => 1000,
        'concurrent_users' => 100
    ],
    'resources' => [
        'cpu_usage' => 70, // percent
        'memory_usage' => 70,
        'disk_io' => 50
    ]
];

Review Process

// Performance review checklist
$performanceChecklist = [
    'database' => [
        'Check for N+1 queries',
        'Verify index usage',
        'Review query complexity',
        'Check result set sizes'
    ],
    'caching' => [
        'Verify cache usage',
        'Check cache keys',
        'Review cache invalidation',
        'Check for cache stampede'
    ],
    'code' => [
        'Review algorithms',
        'Check memory usage',
        'Review loop efficiency',
        'Check for blocking operations'
    ],
    'frontend' => [
        'Check render-blocking',
        'Review asset sizes',
        'Check image optimization',
        'Review DOM operations'
    ]
];

N+1 Query Detection

Identify N+1 Queries

// Bad: N+1 query
$orders = $this->orderCollection->create();

foreach ($orders as $order) {
    // This runs a query for each order
    $customer = $this->customerRepository->get($order->getCustomerId());
    echo $customer->getName();
}

// Good: Join in collection
$orders = $this->orderCollection->create()
    ->join(['customer' => 'customer_entity'], 'customer.entity_id = main_table.customer_id');

foreach ($orders as $order) {
    // No additional query needed
    echo $order->getData('customer_name');
}

// Good: Use resource model
$orders = $this->orderResourceModel->getOrdersWithCustomers();

Detect N+1 in Code Review

// Look for these patterns
$nPlusOnePatterns = [
    // Loop with database call inside
    'foreach.*->get(',
    // Loop with repository call
    'foreach.*Repository->get(',
    // Loop with model load
    'foreach.*Model->load(',
    // Loop with collection addFieldToFilter
    'foreach.*addFieldToFilter('
];

// Example review comment
"// Performance issue: N+1 query detected\n// This loop runs a database query for each order\n// Consider using a join or batch loading\n// See: https://devdocs.magento.com/guides/v2.4/performance-best-practices/algorithm.html"

Fix N+1 Queries

// Option 1: Join in collection
$collection = $this->collectionFactory->create();
$collection->getSelect()->join(
    ['customer' => 'customer_entity'],
    'customer.entity_id = main_table.customer_id',
    ['customer_name' => 'name']
);

// Option 2: Batch loading
$orderIds = array_column($orders, 'entity_id');
$customers = $this->customerRepository->getByIds($orderIds);

// Option 3: Use resource model
$orders = $this->orderResourceModel->getOrdersWithDetails($orderIds);

Missing Index Detection

Identify Missing Indexes

-- Check for full table scans
SELECT * FROM mysql.slow_log
WHERE query LIKE '%catalog_product_entity%'
  AND rows_examined > 10000;

-- Check index usage
SELECT * FROM sys.schema_unused_indexes;

-- Check missing indexes
SELECT * FROM sys.schema_redundant_indexes;

-- Analyze query
EXPLAIN SELECT * FROM catalog_product_entity WHERE sku = '24-WB04';
-- If type = 'ALL', missing index on sku column

Add Missing Indexes

// db_schema.xml
<schema>
    <table name="custom_product">
        <index referenceId="CUSTOM_PRODUCT_SKU" indexType="btree">
            <column name="sku"/>
        </index>
        <index referenceId="CUSTOM_PRODUCT_STATUS_QTY" indexType="btree">
            <column name="status"/>
            <column name="qty"/>
        </index>
    </table>
</schema>

Review Index Strategy

// Index review checklist
$indexChecklist = [
    'primary_key' => 'Always indexed',
    'foreign_keys' => 'Should be indexed',
    'where_clause' => 'Columns in WHERE should be indexed',
    'join_columns' => 'Columns in JOIN should be indexed',
    'order_by' => 'Columns in ORDER BY should be indexed'
];

// Example review
"// Missing index detected\n// Query filters by sku but no index exists\n// Add index: <index referenceId='PRODUCT_SKU'><column name='sku'/></index>"

Caching Strategy Review

Check Cache Usage

// Bad: No caching for expensive operation
public function getProductRecommendations($categoryId)
{
    $products = $this->db->fetchAll(
        'SELECT * FROM recommendations WHERE category_id = ?',
        [$categoryId]
    );
    return $products;
}

// Good: Cache expensive operation
public function getProductRecommendations($categoryId)
{
    $cacheKey = 'recommendations_' . $categoryId;
    $cached = $this->cache->load($cacheKey);
    
    if ($cached) {
        return unserialize($cached);
    }
    
    $products = $this->db->fetchAll(
        'SELECT * FROM recommendations WHERE category_id = ?',
        [$categoryId]
    );
    
    $this->cache->save(serialize($products), $cacheKey, [], 3600);
    return $products;
}

Cache Key Review

// Bad: Inefficient cache keys
$cacheKey = 'product_' . json_encode($filters);

// Good: Deterministic cache keys
$cacheKey = 'product_' . md5(serialize($filters));

// Or use sorted parameters
$cacheKey = 'product_' . implode('_', array_values($filters));

Cache Invalidation Review

// Bad: No cache invalidation
public function saveProduct($product)
{
    $this->resourceModel->save($product);
    // Cache not invalidated!
}

// Good: Cache invalidation on save
public function saveProduct($product)
{
    $this->resourceModel->save($product);
    $this->cache->remove('product_' . $product->getId());
}

// Review: Check all save/update/delete operations
// Ensure cache is invalidated

Algorithm Efficiency Review

Identify Inefficient Algorithms

// Bad: O(n²) algorithm
function findDuplicates($array)
{
    $duplicates = [];
    for ($i = 0; $i < count($array); $i++) {
        for ($j = $i + 1; $j < count($array); $j++) {
            if ($array[$i] === $array[$j]) {
                $duplicates[] = $array[$i];
            }
        }
    }
    return $duplicates;
}

// Good: O(n) algorithm
function findDuplicates($array)
{
    $counts = array_count_values($array);
    return array_keys(array_filter($counts, function($count) {
        return $count > 1;
    }));
}

Memory Leak Detection

// Potential memory leak
class DataProcessor
{
    private $data = []; // Grows indefinitely
    
    public function process($item)
    {
        $this->data[] = $item; // Never cleared
        // Process...
    }
}

// Fix: Clear data after processing
class DataProcessor
{
    private $data = [];
    
    public function process($item)
    {
        $this->data[] = $item;
        $this->processData();
        $this->data = []; // Clear after processing
    }
}

Blocking Operations

// Bad: Blocking operation
public function syncData()
{
    $this->httpClient->setTimeout(30); // Blocks for 30 seconds
    $this->httpClient->get('https://api.example.com/data');
}

// Good: Non-blocking with timeout
public function syncData()
{
    $this->httpClient->setTimeout(5); // Short timeout
    
    try {
        $result = $this->httpClient->get('https://api.example.com/data');
        $this->processData($result);
    } catch (TimeoutException $e) {
        $this->logger->warning('Sync timeout, will retry later');
        $this->queue->sendMessage('sync.retry', ['item' => $item]);
    }
}

Practice Problems

0 / 1 solved
Performance Review Exercise

Review code for N+1 queries, missing indexes, and inefficient algorithms.

Solution
// Findings:
// 1. N+1: Loop with get() → Use join
// 2. Index: sku column not indexed → Add index
// 3. Algorithm: O(n²) duplicate check → Use array_count_values
// 4. Cache: Missing cache → Add cache layer
// Results: 10x performance improvement

Quiz

1. What is an N+1 query?

Question 1 options

2. How to detect missing indexes?

Question 2 options

3. What is cache stampede?

Question 3 options

4. What is the time complexity of O(n²)?

Question 4 options

Flashcards

Question

N+1 query?

Answer

One query + N queries in loop, use joins/batch loading

Question

Missing index detection?

Answer

EXPLAIN shows type='ALL' (full table scan)

Question

Cache stampede?

Answer

Multiple requests rebuild cache simultaneously

Question

O(n²) complexity?

Answer

Quadratic, slow for large datasets

Question

Memory leak sign?

Answer

Memory usage growing indefinitely without cleanup

Revision Notes

Key Takeaways

  • 1. N+1 queries: One + N in loop, use joins/batch
  • 2. Missing indexes: EXPLAIN shows full table scan
  • 3. Cache stampede: Multiple requests rebuild cache
  • 4. O(n²): Quadratic, slow for large datasets
  • 5. Memory leak: Growing usage without cleanup

Interview Tips

  • Explain N+1 query detection
  • Discuss index strategy
  • Know caching best practices
  • Understand algorithm complexity

Cheat Sheet

Performance Review

  • N+1: One + N in loop → Join/batch
  • Index: EXPLAIN → type=ALL → Add index
  • Cache: Stampede → Lock/early expiry
  • O(n²): Quadratic → Optimize
  • Memory: Growing → Clean up