You're reading for free via David Garcia's Friend Link. Become a member to access the best of Medium.

Member-only story

How I Optimise Symfony-based Apps

David Garcia
Stackademic
Published in
14 min readAug 26, 2024
Business candle stick graph chart of stock market investment trading @ iStock

Not a Medium Member? Read this article by clicking here.

Software performance is critical. Users expect applications to be fast, responsive, and reliable. High-performance software enhances user experience, scales better, reduces infrastructure costs, and improves overall efficiency.

Considering performance from the outset is crucial for delivering applications that meet user and business expectations. By continuously refining code and processes, developers ensure that applications remain efficient, maintainable, and capable of handling increasing demands.

Optimised code runs faster and consumes fewer resources, leading to cost savings and improved scalability. Code optimisation during development helps prevent bottlenecks, reduce technical debt, and ensure the software can evolve as requirements grow.

So, this is my steps list to optimise my Symfony Apps:

  1. Profile the code
  2. SQL and Doctrine optimisations
  3. Unleash the benefits of using Elasticsearch
  4. Reuse your code; don't repeat yourself
  5. Asynchronous programming & microservices
  6. Provide real-time updates efficiently
  7. Progressive deployment
  8. PHP project agnostic enhancements

Step 1) Profile the code

Blackfire Profiler @ Chrome Web Store

Profiling the source code of your Symfony applications is not just important, it's crucial. It's a proactive step that helps you identify performance bottlenecks and inefficiencies, allowing you to optimise critical areas and ensure your application runs efficiently.

Profile your code with Blackfire

Blackfire is a user-friendly performance management tool for developers to profile, analyse, and optimise PHP applications (among other programming languages).

It provides detailed insights into how your code executes, identifies bottlenecks, and offers suggestions for improving security and performance.

Moreover, Blackfire's real-time performance monitoring and automated testing capabilities provide developers with a sense of security and confidence in the stability of their applications.

This makes it an invaluable tool for maintaining high performance throughout the development lifecycle.

Profile your code with PHP SPX

The main problem with using Blackfire is its (highly-priced) cost. Paying 33.25€ / month plus VAT, billed annually, means that you need to pay almost 500€ at once

Blackfire’s Development Plan cost:
( 33.25€ / month x 12 months ) + your country’s percentage of VAT
33.25 * 12 * 1.21 = 482,79
https://www.blackfire.io/pricing/

The SPX extension is an open-source profiling tool that allows developers to monitor and analyse the performance of PHP applications. It is an excellent alternative to Blackfire, especially with the Symfony Profiler component.

The SPX extension provides detailed insights into execution times, memory usage, and call graphs, helping developers identify performance bottlenecks.

SPX is lightweight, easy to integrate, and supports real-time profiling, making it a valuable tool for optimising PHP code and improving application efficiency.

Profile your code with the Symfony Profiler Component

The Symfony Profiler Component, a robust add-on tool, is a valuable asset that offers comprehensive insights into the operation of Symfony applications.

It collects a wide array of data on requests, responses, database queries, memory usage, and more, all conveniently accessible through a web interface.

This allows developers to analyse and debug their applications more effectively, identify performance issues, and optimise code for better efficiency.

Caution: due to the inherent risks, Symfony strongly advises against activating the profiler in production environments, as it could expose your project to significant security vulnerabilities.

Step 2) SQL and Doctrine optimisations

Using the aws_rds_read_iops_average and aws_rds_write_iops_average metrics @ sysdig.com

Monitoring, refactoring, and optimising SQL queries is critical, especially when using a database abstraction library like Doctrine, because inefficient queries can lead to significant performance issues.

Poorly optimised SQL queries can cause slow response times, excessive resource usage, database bottlenecks, and table locks!

By refining your queries, you can ensure that your application interacts efficiently with the database, enhancing overall performance, scalability, and user experience.

Common SQL Query Optimisations

Image created by the author by using the service hosted at ray.so

Select Specific Columns instead of Select Everything (*):

When selecting only the required columns (or properties) instead of all columns (or the entire entity), we can reduce the amount of data transferred from the database, leading to faster query execution and lower memory usage.

Use JOIN Constraints Instead of Sub-Queries:

Joins, a more efficient alternative to sub-queries, allow you to optimise the query execution plan, thereby reducing time complexity and enhancing performance. This approach will give you control over your query optimisation skills.

Avoid N+1 Query Problem:

Instead of running multiple queries within loops, use a single query with a join or IN clause to fetch all necessary data in one go. This will reduce database load and improve performance.

Be Cautious with Too Many INNER/LEFT/RIGHT JOIN or IN Constraints:

Be aware of the potential performance bottlenecks arising from excessive joins or significant data constraints. Each join adds overhead, and when too many tables are joined (or if these contain millions of rows), it can slow down query execution, sometimes making sub-queries or multiple simpler queries more efficient. This awareness will help you navigate potential performance issues with confidence.

Use the Equal Sign (=) Instead of LIKE for Text Comparisons:

The = operator is faster than LIKE because LIKE performs pattern matching, which is more computationally expensive. Use LIKE only when necessary, such as partial matches in text fields or searching for patterns within data. And, if this is a typical case for your Symfony application, consider search engines like Elasticsearch.

Add Indexing for Commonly Used Columns:

Proper indexing significantly speeds up data retrieval by reducing the amount of data the database needs to scan. Focus on indexing columns frequently used in WHERE, JOIN, and ORDER BY clauses.

Limit and Offset with Pagination:

Using LIMIT and OFFSET with pagination can minimise the data processed and sent to the client, improving response times for large datasets.

OOP vs Array Hydration in Doctrine

Image created by the author by using the service hosted at ray.so

In a nutshell, use OOP hydration for richer interaction with entities and their logic and array hydration for lighter, performance-oriented queries or when working with raw data.

Object-Oriented Programming Hydration (Doctrine entities):

This method returns results as instances of your entity classes. Doctrine automatically maps database rows to the properties of your PHP objects.

Use OOP hydration when working with entities that include methods and business logic. It's ideal for most applications where you benefit from leveraging entity behaviour, relationships, and validations.

Array Hydration (raw PHP array):

This method returns results as arrays instead of objects. Each row from the database is represented as an associative array.

Array hydration is useful for performance optimisation when you don't need the overhead of entity objects or when working with large datasets. It's also helpful for simple data retrieval tasks or integrating with components requiring array data.

Doctrine's Cache Overview

Image created by the author by using the service hosted at ray.so

Doctrine's cache can boost performance for read-heavy, stable data, but it should be avoided for frequently changing or sensitive information by ensuring proper cache key management and data segmentation.

Doctrine's cache improves performance by storing the results of database queries, metadata, and query plans, reducing the need for repetitive database access. This can significantly speed up application performance by avoiding expensive database operations.

When to Use It:

  • Read-Heavy Applications: This is ideal for applications with frequent read operations and infrequent updates, where caching can reduce database load.
  • Static Data: Useful for caching metadata or configurations that do not change often.

When to Avoid It:

  • Frequent Updates: Avoid caching for data that changes often, as the cache might become stale and lead to inconsistent results.
  • Sensitive Data: Be cautious with user-specific or sensitive data. Ensure that cached data is scoped properly to avoid exposing sensitive information.

User-Specific Data Handling:

  • Cache Segmentation: When dealing with user-specific data, use cache keys, including user identifiers or session data, to ensure that each user's cache is isolated. For example, cache data with keys like user_{user_id}_data to prevent one user from accessing another user's cached data.

Step 3) Unleash the benefits of using Elasticsearch

Comparison between Elasticsearch and the Default Magento Search. Numbers represent SQL queries of the layered navigation block (lower numbers equal higher page speed)

Elasticsearch is a powerful search and analytics engine designed for fast and scalable text search capabilities.

It excels at full-text search, real-time indexing, and complex querying, making it ideal for applications that require quick, sophisticated search functionalities and detailed analysis of text data.

It can handle large volumes of data and provides advanced features like relevancy scoring and complex filtering, which enhances search accuracy and performance.

Elasticsearch vs NoSQL Databases

  • Purpose: Elasticsearch is primarily a search and analytics engine optimised for full-text search and complex queries. In contrast, NoSQL databases like MongoDB are general-purpose databases designed for flexible data storage, high scalability, and diverse data models.
  • Capabilities: Elasticsearch offers advanced search capabilities and analytics on large datasets with low latency, while NoSQL databases focus on high-speed data retrieval and flexibility in data storage.

Benefits of Using FOS Elastica Bundle with Symfony

  • The FOS Elastica Bundle integrates Elasticsearch with Symfony applications, providing a straightforward way to leverage Elasticsearch's search capabilities. It simplifies the configuration, indexing, and querying of data within Symfony projects, offering a higher-level abstraction over Elasticsearch's native API.
  • This bundle simplifies implementing advanced search features, managing indices, and keeping search data in sync with your Symfony application's entities.

When to Use Elasticsearch SDK Instead of FOS Elastica Bundle

  • Performance & Flexibility: The Elasticsearch SDK is recommended for performance-critical applications or when you need fine-grained control over Elasticsearch operations. It allows direct interaction with Elasticsearch's low-level API and advanced features.
  • Complex Scenarios: If you have highly customised search requirements or need to optimise performance beyond what the FOS Elastica Bundle offers, the SDK provides more flexibility and detailed control, potentially leading to better performance for complex queries or large-scale data handling.

Step 4) Reuse Your Code and Don't Repeat Yourself

Image created by the author by using the service hosted at ray.so

Code Reusability refers to designing software components or functions to be used across different parts of an application or in other projects without modification.

Legacy projects are usually full of duplicated code.

This approach helps avoid duplication, reduces development time, and enhances maintainability by allowing developers to leverage existing code efficiently while reducing technical debt. Developers only need to change the source code once, and the changes take effect immediately everywhere in the entire application.

Symfony Empowers Code Reusability with Dependency Injection

Symfony's Dependency Injection system promotes code reusability by allowing developers to manage and inject dependencies into services and components through the constructors.

This means that components can be easily reused and tested independently, as their dependencies are provided externally rather than hardcoded.

By defining services in configuration files and injecting them where needed, Symfony encourages the creation of modular, interchangeable components, enhancing overall code flexibility and reusability.

Reusing Existing Source Code with the Event Dispatcher

Symfony's Event Dispatcher facilitates code reusability by allowing components to communicate and extend functionality through events.

Components can listen for specific events and respond accordingly without tightly coupling the code. Existing source code can be reused and extended by subscribing to or dispatching events, enabling custom behaviour and interaction without modifying the core codebase.

It promotes modularity and flexibility, as different application parts can interact through well-defined events.

Step 5) Asynchronous programming & microservices

Using RabbitMQ in A Symfony Application Through Messenger Component

Asynchronous Programming allows tasks to run concurrently without waiting for others to finish, improving efficiency and responsiveness.

Microservices leverage asynchronous programming to handle multiple requests simultaneously, communicate without blocking, and scale efficiently, resulting in faster performance and reduced latency.

Choose your provider: RabbitMQ vs AWS SQS (and others)

RabbitMQ is an open-source message broker that supports multiple messaging protocols, allowing for flexible, real-time message routing and delivery. It requires managing your own infrastructure but offers advanced features like message prioritisation, routing, and exchanges.

AWS SQS (Simple Queue Service) is a fully managed message queuing service by AWS that provides a simple, scalable, and reliable way to decouple components of a cloud application. It doesn't require infrastructure management but could be more feature-rich, focusing on scalability, durability, and simplicity.

Main Differences:

  • Management: RabbitMQ requires self-management; Amazon fully manages AWS SQS.
  • Features: RabbitMQ offers more advanced messaging features; SQS prioritises simplicity and scalability.
  • Flexibility: RabbitMQ supports multiple protocols and complex routing; SQS is easier to integrate with AWS services.
  • Timing: RabbitMQ doesn't break an active job if you don't acknowledge the reception of that job immediately; SQS gives you a reasonable threshold, but it can block long-run operations if they are not acknowledged.

Choose your integration: Symfony Messenger vs Enqueue

Symfony Messenger is a component within Symfony that provides a unified way to handle asynchronous tasks and messages while supporting a wide range of message brokers (even a database).

Enqueue Library is a more general-purpose library for queuing and background processing that supports various message brokers. It offers more flexibility for complex setups and can be used with or without Symfony.

Symfony Messenger is tightly integrated with Symfony and designed for simplicity within the Symfony ecosystem, while Enqueue is more flexible and can be used across different frameworks and environments.

Step 6) Provide real-time updates efficiently

Is Real-time updating really as important as you think?

As mentioned at the top of this article, users expect applications to be fast, responsive, and reliable. This means they want real-time updates.

When real-time applications don't use real-time notification protocols, they rely on internal timers and repetitive AJAX/API calls (polling) to check for updates.

This approach increases server load and network traffic, leading to inefficient resource usage, higher latency, and slower response times.

It can also degrade user experience by causing unnecessary delays and making the application less responsive to real-time changes.

Mercure.rocks: Real-time APIs Made Easy

Mercure is a real-time update protocol and server designed to efficiently push updates to web and mobile apps in a convenient, fast, reliable and battery-efficient way.

It simplifies the implementation of real-time features by using the publish-subscribe pattern. This pattern allows servers to push data to clients as soon as changes occur without constant polling.

This enhances the performance and responsiveness of applications that need to deliver real-time notifications, like chat apps or live feeds.

Step 7) Progressive deployment

Intro to Deployment Strategies: Blue-Green, Canary, and More

Progressive Deployment is a strategy where new features or versions of an application are gradually released to a subset of users before being rolled out to everyone. This approach helps identify and fix issues early, reducing the risk of widespread problems.

Harness the power of Feature Toggle Flags

Feature Flags support progressive deployment by allowing developers to toggle new features on or off for specific user groups. This enables controlled rollouts, A/B testing, and quick rollbacks if needed, ensuring a smoother and safer deployment process when introducing new features.

Step 8) PHP project agnostic enhancements

Improving PHP Performance for Web Applications

As usual, there is a set of recommendations that are not entirely focused on Symfony but that you can use to improve the performance of your web applications.

The primary points I would like to highlight are the following:

OPCache + OPCache Preload

OPCache is a PHP extension that caches the compiled bytecode of PHP scripts, reducing the need for recompilation on every request and significantly improving performance.

OPCache Preload extends this by loading and caching specific scripts at server startup, keeping them in memory throughout the server's runtime.

This reduces load times by eliminating the need to repeatedly fetch and compile frequently used code, boosting application speed.

Symfony provides a specific pre-cached file that you can pass to your OPCache Preload setting, enhancing your Symfony App performance.

Cached Data through Memcached or Redis

Memcached and Redis are in-memory data stores that cache frequently accessed data, such as database query results or session data, to speed up retrieval times.

By storing data in memory, these caching systems reduce the load on databases and improve the overall response time of PHP applications.

Redis also offers persistence and advanced data structures, making it a versatile option for caching and beyond.

Use Composer for Optimised Loading Time and Resource Usage

Composer is a dependency manager for PHP that automates the inclusion of external libraries and packages.

Its autoloading feature dynamically loads classes only when needed rather than all classes upfront.

This reduces memory usage and improves the loading time of PHP applications by ensuring that only necessary resources are loaded, optimising performance and resource management.

Please clap and follow!

👏 Enjoyed this article? Please give it a round of applause by clicking the 👏 button below. Your support means the world to me!

📚 Want to stay updated with my latest posts? Hit the "Follow" button to join my community and never miss out.

Thank you for reading and engaging! Your feedback and support inspire me to share more valuable insights with you. 🙌

Stackademic 🎓

Thank you for reading until the end. Before you go:

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Written by David Garcia

Senior Software Engineer, Backend, NodeJS & Symfony developer, workaholic, passionate for new technologies and OSS contributor. https://linktr.ee/davidgarciacat

No responses yet

Write a response