Optimizing a Product Catalog Microservice with Profiling Tools in PHP Using DDEV and Visual Studio Code

Optimizing a Product Catalog Microservice with Profiling Tools in PHP Using DDEV and Visual Studio Code

On17th Nov 2024, 2024-12-04T09:52:41+05:30 ByKarthik Kumar D K | read
Listen Pause Resume Stop

In this article, we will enhance the product catalog microservice we built earlier by integrating Xdebug for profiling, specifically tailored for development in Visual Studio Code (VS Code). We will include detailed setup instructions, ensuring a smooth experience while profiling your application.

Overview

Key Components

  1. PHP 8: The programming language for our microservice.
  2. MySQL: The database for storing product data.
  3. Redis: The caching layer for frequently accessed product data.
  4. Xdebug: A profiling tool for identifying performance bottlenecks.

Step 1: Setting Up Your Environment with DDEV

1. Install DDEV

Ensure DDEV is installed on your machine. Follow the installation instructions here.

2. Create a New DDEV Project

Open your terminal and create a new project directory:

mkdir product-catalog-microservice

cd product-catalog-microservice

Initialize DDEV:

ddev config

Choose "php" as the project type and select PHP 8.

Start the DDEV environment:

ddev start

3. Install Composer Dependencies

Inside the DDEV environment, run:

ddev composer require predis/predis

If you see isssue with predis/predis, setup redis with docker compose, refer to this article for more details.

Step 2: Setting Up the MySQL Database

Create the Database

DDEV automatically sets up a MySQL database for you. Access the MySQL shell with:

ddev mysql

Run the following commands to create the products table:

CREATE TABLE products (

    id INT AUTO_INCREMENT PRIMARY KEY,

    name VARCHAR(255) NOT NULL,

    description TEXT,

    price DECIMAL(10, 2) NOT NULL,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

);

Insert Sample Data

Insert some sample products:

INSERT INTO products (name, description, price) VALUES

('Product A', 'Description for Product A', 10.00),

('Product B', 'Description for Product B', 20.00),

('Product C', 'Description for Product C', 30.00);

Step 3: Implementing the Microservice

Create the Entry Point

Create an index.php file in the web directory of your DDEV project (./web/index.php):

require 'vendor/autoload.php';

use Predis\Client;

class ProductCatalog {

    private Client $cache;

    private PDO $db;

    public function __construct() {

        $this->cache = new Client(['host' => 'redis']);

        $this->db = new PDO('mysql:host=db;dbname=db', 'db', 'db'); // DDEV uses 'db' as the default MySQL host

    }

    public function getProducts() {

        $cacheKey = 'product_catalog';

        // Check cache

        $cachedProducts = $this->cache->get($cacheKey);

        if ($cachedProducts) {

            return json_decode($cachedProducts, true);

        }

        // Fetch from database

        $stmt = $this->db->query("SELECT * FROM products");

        $products = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Store in cache

        $this->cache->set($cacheKey, json_encode($products), 'EX', 3600); // Cache for 1 hour

        return $products;

    }

    public function getProduct($id) {

        $stmt = $this->db->prepare("SELECT * FROM products WHERE id = :id");

        $stmt->execute(['id' => $id]);

        return $stmt->fetch(PDO::FETCH_ASSOC);

    }

    public function createProduct($name, $description, $price) {

        $stmt = $this->db->prepare("INSERT INTO products (name, description, price) VALUES (:name, :description, :price)");

        $stmt->execute(['name' => $name, 'description' => $description, 'price' => $price]);

        // Clear cache

        $this->cache->del('product_catalog');

        return $this->db->lastInsertId();

    }

    public function updateProduct($id, $name, $description, $price) {

        $stmt = $this->db->prepare("UPDATE products SET name = :name, description = :description, price = :price WHERE id = :id");

        $stmt->execute(['id' => $id, 'name' => $name, 'description' => $description, 'price' => $price]);

        // Clear cache

        $this->cache->del('product_catalog');

        return $stmt->rowCount();

    }

    public function deleteProduct($id) {

        $stmt = $this->db->prepare("DELETE FROM products WHERE id = :id");

        $stmt->execute(['id' => $id]);

        // Clear cache

        $this->cache->del('product_catalog');

        return $stmt->rowCount();

    }

}

// Handle requests

$requestMethod = $_SERVER['REQUEST_METHOD'];

$catalog = new ProductCatalog();

switch ($requestMethod) {

    case 'GET':

        if (isset($_GET['id'])) {

            $product = $catalog->getProduct($_GET['id']);

            header('Content-Type: application/json');

            echo json_encode($product);

        } else {

            $products = $catalog->getProducts();

            header('Content-Type: application/json');

            echo json_encode($products);

        }

        break;

    case 'POST':

        $data = json_decode(file_get_contents('php://input'), true);

        $id = $catalog->createProduct($data['name'], $data['description'], $data['price']);

        header('HTTP/1.1 201 Created');

        echo json_encode(['id' => $id]);

        break;

    case 'PUT':

        $data = json_decode(file_get_contents('php://input'), true);

        $updated = $catalog->updateProduct($data['id'], $data['name'], $data['description'], $data['price']);

        header('Content-Type: application/json');

        echo json_encode(['updated' => $updated > 0]);

        break;

    case 'DELETE':

        $id = $_GET['id'] ?? null;

        if ($id) {

            $deleted = $catalog->deleteProduct($id);

            header('Content-Type: application/json');

            echo json_encode(['deleted' => $deleted > 0]);

        } else {

            header('HTTP/1.1 400 Bad Request');

            echo json_encode(['error' => 'Product ID is required']);

        }

        break;

    default:

        header('HTTP/1.1 405 Method Not Allowed');

        break;

}

Explanation of the Code

  • ProductCatalog Class: This class manages product data, including methods to get all products, get a specific product, create, update, and delete products. It interacts with both MySQL and Redis.
  • Caching: The Redis cache is used to store the list of products to reduce database queries. The cache is cleared when a product is created, updated, or deleted.
  • HTTP Methods: The microservice handles different HTTP methods (GET, POST, PUT, DELETE) to allow full CRUD operations.

Step 4: Testing the Microservice

You can test your microservice by accessing it through the DDEV URL. Run:

ddev launch

This will open your default web browser to the project URL. You can test the endpoints using cURL or tools like Postman.

Example cURL Commands

GET: Get all products

curl http://.ddev.site

GET: Get a specific product

curl http://.ddev.site?id=1

POST: Create a new product

curl -X POST http://.ddev.site -H "Content-Type: application/json" -d '{"name":"Product D","description":"Description for Product D","price":40.00}'

PUT: Update a product

curl -X PUT http://.ddev.site -H "Content-Type: application/json" -d '{"id":1,"name":"Updated Product A","description":"Updated Description","price":15.00}'

DELETE: Delete a product

curl -X DELETE http://.ddev.site?id=1

Step 5: Setting Up Xdebug for Profiling

1. Install Xdebug

To install Xdebug in your DDEV environment, run:

ddev ssh

pecl install xdebug

2. Configure Xdebug

Edit the Xdebug configuration by running:

ddev ssh

nano /etc/php/8.0/fpm/conf.d/20-xdebug.ini

Add the following configuration:

zend_extension=xdebug.so

xdebug.mode=profile

xdebug.output_dir=/var/www/html/xdebug

xdebug.profiler_enable=1

xdebug.profiler_output_name=cachegrind.out.%p

Settings Explained:

  • xdebug.mode=profile: Enables profiling.
  • xdebug.output_dir: Directory where profiling files are saved.
  • xdebug.profiler_enable=1: Enables profiling for all requests.
  • xdebug.profiler_output_name: Naming convention for profiling files.

3. Restart DDEV

Restart the DDEV environment:

ddev restart

Step 6: Setting Up Visual Studio Code for Profiling

1. Install the PHP Debug Extension

In Visual Studio Code, install the PHP Debug extension by Felix Becker. This extension enables you to debug PHP applications using Xdebug.

2. Configure Debug Settings

Create a debug configuration for Xdebug:

  1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P) and select Debug: Open launch.json.
  2. If prompted, select PHP. This will create a default launch.json file.

Edit launch.json to match the following configuration:

{

    "version": "0.2.0",

    "configurations": [

        {

            "name": "Listen for XDebug",

            "type": "php",

            "request": "launch",

            "port": 9003,

            "pathMappings": {

                "/var/www/html": "${workspaceFolder}/web"

            }

        }

    ]

}

Settings Explained:

  • port: Ensure this matches the port configured in Xdebug.
  • pathMappings: Maps paths in the Docker container to your local filesystem.

3. Start Listening for Debugging

  1. In VS Code, go to the Debug view (click on the Debug icon on the left sidebar).
  2. Select Listen for XDebug from the dropdown.
  3. Click the green play button to start listening for Xdebug connections.

Step 7: Generating and Analyzing Profiling Data

1. Run Your Microservice

Access your microservice as usual by launching it:

ddev launch

2. Generate Profiling Data

As you interact with the microservice (e.g., fetching products, adding new products), Xdebug will generate profiling files in the /var/www/html/xdebug directory.

3. Analyze Profiling Data

To analyze the generated profiling data:

  1. QCacheGrind: Install it on your machine and open the profiling files generated in the xdebug directory.
  2. Webgrind: Alternatively, you can set up Webgrind as a web-based tool for analyzing profiling data.

Common Issues and Troubleshooting

1. Profiling Files Not Generated

  • Possible Causes:
    • Xdebug is not installed or configured correctly.
    • xdebug.profiler_enable is not set to 1.
  • Solution: Double-check the configuration and ensure that the output directory is writable.

2. High Memory Usage

  • Possible Causes:Inefficient code or excessive data being processed.
  • Solution: Use profiling data to identify memory-intensive functions and optimize them.

3. Slower Performance Due to Profiling

  • Possible Causes: Profiling adds overhead that can slow down your application.
  • Solution: Disable profiling in production environments by setting xdebug.profiler_enable=0 in your php.ini.

FAQs

Q1: Can I disable profiling for specific requests?

Yes, you can disable profiling by adding ?XDEBUG_PROFILE=0 to your URL.

Q2: Is it safe to use Xdebug in a production environment?

Xdebug should not be used in production due to performance overhead and potential security risks. Always disable it in production.

Q3: How can I view profiling data without installing QCacheGrind?

You can use Webgrind, which is a web-based interface for Xdebug profiling data. Install it by downloading from Webgrind GitHub.

Q4: What should I focus on when optimizing my application?

When analyzing profiling data, focus on:

  • Functions with high execution times.
  • Functions that are called frequently (N+1 problems).
  • Memory usage hotspots.

Conclusion

In this article, we have set up Xdebug for profiling the product catalog microservice, tailored specifically for use with Visual Studio Code. By following these steps, you can effectively identify and address performance bottlenecks in your application, ensuring optimal performance for your users.

Thanks for reading the article, for more Science & Technology related articles read and subscribe to peoples blog articles.

Labels


Related Articles

Recent Articles

Recent Quick Read

Recent Great People

We Need Your Consent
By clicking “Accept Cookies”, you agree to the storing of cookies on your device to enhance your site navigation experience.
I Accept Cookies