Paso 29: Gestionando el rendimiento
Gestionando el rendimiento¶
Optimizar antes de tiempo es la raíz de todos los males.
Tal vez ya hayas leído esta cita antes. Pero me gusta citarla en su totalidad:
Deberíamos olvidar las pequeñas optimizaciones el 97 % de las veces; optimizar antes de tiempo es el origen de todos los males. Sin embargo, no deberíamos dejar pasar la oportunidad de optimizar ese crucial 3 %.
—Donald Knuth
Incluso pequeñas mejoras en el rendimiento pueden marcar la diferencia, especialmente para sitios de comercio electrónico. Ahora que la aplicación del libro de visitas está lista para el gran público, veamos cómo podemos comprobar su rendimiento.
La mejor manera de encontrar optimizaciones de rendimiento es usar un analizador de rendimiento o perfilador (profiler). La opción más popular hoy en día es Blackfire (aviso a navegantes: también soy el fundador del proyecto Blackfire).
Introducción a Blackfire¶
Blackfire se compone de varias partes:
- Un cliente que activa perfiles (la herramienta CLI de Blackfire o una extensión de navegador para Google Chrome o Firefox);
- Un agente que prepara y agrega datos antes de enviarlos a blackfire.io para su visualización;
- Una extensión PHP (que actúa a modo de sonda) y que analiza el código PHP.
Para trabajar con Blackfire, primero tienes que registrarte .
Instala Blackfire en tu equipo local, ejecutando el siguiente script de instalación rápida:
1 | $ curl https://installer.blackfire.io/ | bash
|
Este instalador descarga la herramienta de línea de comandos de Blackfire, y luego instala la sonda PHP (sin habilitarla) en todas las versiones disponibles de PHP.
Habilita la sonda PHP para nuestro proyecto:
1 2 3 4 5 6 7 8 9 10 | --- a/php.ini
+++ b/php.ini
@@ -7,3 +7,7 @@ session.use_strict_mode=On
realpath_cache_ttl=3600
zend.detect_unicode=Off
xdebug.file_link_format=vscode://file/%f:%l
+
+[blackfire]
+# use php_blackfire.dll on Windows
+extension=blackfire.so
|
Reinicia el servidor web para que PHP pueda cargar Blackfire:
1 2 | $ symfony server:stop
$ symfony server:start -d
|
La herramienta de línea de comandos de Blackfire necesita estar configurada con tus credenciales personales de cliente (para almacenar los perfiles de tus proyectos en tu cuenta personal). Encuéntralas en la parte superior de la página ``Settings/Credentials` (configuración/credenciales) <https://blackfire.io/my/settings/credentials>`_ y ejecuta el siguiente comando reemplazando los marcadores de posición:
1 | $ blackfire config --client-id=xxx --client-token=xxx
|
Nota
Para obtener instrucciones completas de instalación, sigue la guía oficial de instalación detallada. Son muy útiles cuando se instala Blackfire en un servidor.
Configuración del agente Blackfire en Docker¶
El último paso es añadir el servicio del agente Blackfire en el stack (pila) de servicios de Docker Compose:
1 2 3 4 5 6 7 8 9 10 11 | --- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -20,3 +20,8 @@ services:
mailer:
image: schickling/mailcatcher
ports: [1025, 1080]
+
+ blackfire:
+ image: blackfire/blackfire
+ env_file: .env.local
+ ports: [8707]
|
Para comunicarse con el servidor, necesitas obtener tus credenciales personales del servidor (estas credenciales identifican dónde deseas almacenar los perfiles; puedes crear una por proyecto); se pueden encontrar en la parte inferior de la página Settings/Credentials
https://blackfire.io/my/settings/credentials. Almacénalas en un archivo local .env.local
:
1 2 | BLACKFIRE_SERVER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BLACKFIRE_SERVER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
Ahora puedes lanzar el nuevo contenedor:
1 2 | $ docker-compose stop
$ docker-compose up -d
|
Arreglando una instalación de Blackfire que no funciona¶
Si obtienes un error durante el análisis de rendimiento, aumenta el nivel de registro de Blackfire para obtener más información:
1 2 3 4 5 6 7 | --- a/php.ini
+++ b/php.ini
@@ -10,3 +10,4 @@ zend.detect_unicode=Off
[blackfire]
# use php_blackfire.dll on Windows
extension=blackfire.so
+blackfire.log_level=4
|
Reinicia el servidor web:
1 2 | $ symfony server:stop
$ symfony server:start -d
|
Y revisa los logs en vivo (en tiempo real):
1 | $ symfony server:log
|
Analiza la aplicación de nuevo y comprueba la salida del log.
Configurando Blackfire en producción¶
Blackfire está incluido por defecto en todos los proyectos de SymfonyCloud.
Configura las credenciales del servidor como variables de entorno:
1 2 | $ symfony var:set BLACKFIRE_SERVER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ symfony var:set BLACKFIRE_SERVER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
Y habilita la sonda PHP como cualquier otra extensión PHP:
1 2 3 4 5 6 7 8 9 10 | --- a/.symfony.cloud.yaml
+++ b/.symfony.cloud.yaml
@@ -4,6 +4,7 @@ type: php:7.4
runtime:
extensions:
+ - blackfire
- xsl
- amqp
- redis
|
Configurando Varnish para Blackfire¶
Antes de que puedas desplegar para empezar a realizar las tareas de análisis, necesitas una forma de evitar la caché HTTP de Varnish. Si no, Blackfire nunca llegará a la aplicación PHP. Vas a autorizar solo las peticiones de profiling que provengan de tu máquina local.
Encuentra tu dirección IP actual:
1 | $ curl https://ifconfig.me/
|
y úsala para configurar Varnish:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | --- a/.symfony/config.vcl
+++ b/.symfony/config.vcl
@@ -1,3 +1,11 @@
+acl profile {
+ # Authorize the local IP address (replace with the IP found above)
+ "a.b.c.d";
+ # Authorize Blackfire servers
+ "46.51.168.2";
+ "54.75.240.245";
+}
+
sub vcl_recv {
set req.backend_hint = application.backend();
set req.http.Surrogate-Capability = "abc=ESI/1.0";
@@ -8,6 +16,16 @@ sub vcl_recv {
}
return (purge);
}
+
+ # Don't profile ESI requests
+ if (req.esi_level > 0) {
+ unset req.http.X-Blackfire-Query;
+ }
+
+ # Bypass Varnish when the profile request comes from a known IP
+ if (req.http.X-Blackfire-Query && client.ip ~ profile) {
+ return (pass);
+ }
}
sub vcl_backend_response {
|
Ahora puedes realizar el despliegue.
Analizando el rendimiento de páginas web¶
Puedes hacer análisis de páginas web tradicionales desde Firefox o Google Chrome a través de sus extensiones dedicadas.
No olvides desactivar la caché HTTP en public/index.php
cuando realices el análisis en tu máquina local: si no lo haces, analizarás la capa de caché HTTP de Symfony en lugar de tu propio código:
1 2 3 4 5 6 7 8 9 10 11 | --- a/public/index.php
+++ b/public/index.php
@@ -24,7 +24,7 @@ if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
if ('dev' === $kernel->getEnvironment()) {
- $kernel = new HttpCache($kernel);
+// $kernel = new HttpCache($kernel);
}
$request = Request::createFromGlobals();
|
Para tener una mejor idea del rendimiento de tu aplicación en producción, deberías también hacer un análisis del entorno de «producción». Por defecto, tu entorno local utiliza el entorno de «desarrollo», que añade una sobrecarga significativa (principalmente para recopilar datos para la barra de herramientas de depuración web y el analizador de rendimiento de Symfony).
El cambio de tu máquina local al entorno de producción se puede hacer cambiando la variable de entorno APP_ENV
en el archivo .env.local
:
1 | APP_ENV=prod
|
O puedes usar el comando server:prod
:
1 | $ symfony server:prod
|
No olvides volver a cambiar al entorno de desarrollo cuando finalice tu sesión de profiling (análisis de rendimiento):
1 | $ symfony server:prod --off
|
Analizando el rendimiento de los recursos de la API¶
El análisis de rendimiento de la API o la SPA se realiza mejor en la línea de comandos usando la herramienta Blackfire que se ha instalado previamente:
1 | $ blackfire curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`api
|
El comando blackfire curl
acepta exactamente los mismos argumentos y opciones que cURL.
Comparación del rendimiento¶
En el paso acerca de la «Caché» añadimos una capa de caché para mejorar el rendimiento de nuestro código, pero no hemos comprobado ni medido el impacto en el rendimiento que ha tenido el cambio. Como las personas somos poco efectivas adivinando qué será rápido y qué será lento, podrías terminar en una situación en la que añadir un poco de optimización hiciera que tu aplicación fuera, realmente, más lenta.
Deberías medir siempre el impacto de cualquier optimización que hagas con un analizador de rendimiento. Blackfire lo hace visualmente más fácil gracias a su función de comparación.
Escribiendo pruebas funcionales de caja negra¶
Hemos visto cómo escribir pruebas funcionales con Symfony. Blackfire se puede utilizar para escribir escenarios de navegación que se pueden ejecutar bajo demanda a través del Blackfire player. Escribamos un escenario que envíe un nuevo comentario y lo valide en desarrollo a través del enlace de correo electrónico y a través del admin en producción.
Crea un archivo de nombre .blackfire.yaml
con el siguiente contenido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | scenarios: |
#!blackfire-player
group login
visit url('/login')
submit button("Sign in")
param username "admin"
param password "admin"
expect status_code() == 302
scenario
name "Submit a comment on the Amsterdam conference page"
include login
visit url('/fr/conference/amsterdam-2019')
expect status_code() == 200
submit button("Submit")
param comment_form[author] 'Fabien'
param comment_form[email] '[email protected]'
param comment_form[text] 'Such a good conference!'
param comment_form[photo] file(fake('image', '/tmp', 400, 300, 'cats'), 'awesome-cat.jpg')
expect status_code() == 302
follow
expect status_code() == 200
expect not(body() matches "/Such a good conference/")
# Wait for the workflow to validate the submissions
wait 5000
when env != "prod"
visit url(webmail_url ~ '/messages')
expect status_code() == 200
set message_ids json("[*].id")
with message_id in message_ids
visit url(webmail_url ~ '/messages/' ~ message_id ~ '.html')
expect status_code() == 200
set accept_url css("table a").first().attr("href")
visit url(accept_url)
# we don't check the status code as we can deal
# with "old" messages which do not exist anymore
# in the DB (would be a 404 then)
when env == "prod"
visit url('/admin/?entity=Comment&action=list')
expect status_code() == 200
set comment_ids css('table.table tbody tr').extract('data-id')
with id in comment_ids
visit url('/admin/comment/review/' ~ id)
# we don't check the status code as we scan all comments,
# including the ones already reviewed
visit url('/fr/')
wait 5000
visit url('/fr/conference/amsterdam-2019')
expect body() matches "/Such a good conference/"
|
Descarga el reproductor Blackfire para poder ejecutar el escenario localmente:
1 2 | $ curl -OLsS https://get.blackfire.io/blackfire-player.phar
$ chmod +x blackfire-player.phar
|
Ejecuta este escenario en desarrollo:
1 | $ ./blackfire-player.phar run --endpoint=`symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL` .blackfire.yaml --variable "webmail_url=`symfony var:export MAILER_WEB_URL 2>/dev/null`" --variable="env=dev"
|
O en producción:
1 | $ ./blackfire-player.phar run --endpoint=`symfony env:urls --first` .blackfire.yaml --variable "webmail_url=NONE" --variable="env=prod"
|
Los escenarios de Blackfire también pueden desencadenar perfiles para cada solicitud y ejecutar pruebas de rendimiento añadiendo la opción --blackfire
.
Automatizando los análisis de rendimiento¶
La gestión del rendimiento no sólo consiste en mejorar el rendimiento del código existente, sino también en comprobar que no se introducen regresiones de rendimiento.
El escenario escrito en la sección anterior puede ejecutarse automáticamente en un workflow de integración continua (CI), o en producción, de forma regular.
En SymfonyCloud, también se permite ejecutar los escenarios cada vez que se crea una nueva rama o se despliega en producción para analizar automáticamente el rendimiento del nuevo código.
- « Previous Paso 28: Internacionalizando una aplicación
- Next » Paso 30: Descubriendo el corazón de Symfony
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.