Passo 29: Lidando com o Desempenho

5.0 version
Maintained

Lidando com o Desempenho

Otimização prematura é a raiz de todo o mal.

Talvez você já tenha lido esta citação antes. Mas gosto de citá-la na íntegra:

Devemos esquecer as pequenas eficiências, digamos, 97% das vezes: a otimização prematura é a raiz de todo o mal. No entanto, não devemos desperdiçar nossas oportunidades naqueles 3% críticos.

—Donald Knuth

Mesmo pequenas melhorias de desempenho podem fazer a diferença, especialmente para sites de e-commerce. Agora que a aplicação do livro de visitas está pronta para o horário nobre, vamos ver como podemos verificar o seu desempenho.

A melhor maneira de encontrar otimizações de desempenho é usar um profiler. A opção mais popular hoje em dia é o Blackfire (aviso: eu também sou o fundador do projeto Blackfire).

Apresentamos o Blackfire

O Blackfire é composto de várias partes:

  • Um cliente que aciona profiles (a Ferramenta CLI do Blackfire ou uma extensão de navegador para Google Chrome ou Firefox);
  • Um agente que prepara e agrega dados antes de enviá-los para blackfire.io para exibição;
  • Uma extensão PHP (a sonda) que instrumenta o código PHP.

Para trabalhar com o Blackfire, você precisa primeiro criar uma conta.

Instale o Blackfire em sua máquina local executando o seguinte script de instalação rápida:

1
$ curl https://installer.blackfire.io/ | bash

Este instalador baixa a Ferramenta CLI do Blackfire e depois instala a sonda PHP (sem ativá-la) em todas as versões disponíveis do PHP.

Habilite a sonda PHP para nosso projeto:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
--- a/php.ini
+++ b/php.ini
@@ -6,3 +6,7 @@ max_execution_time=30
 session.use_strict_mode=On
 realpath_cache_ttl=3600
 zend.detect_unicode=Off
+
+[blackfire]
+# use php_blackfire.dll on Windows
+extension=blackfire.so

Reinicie o servidor web para que o PHP possa carregar o Blackfire:

1
2
$ symfony server:stop
$ symfony server:start -d

A Ferramenta CLI do Blackfire precisa ser configurada com suas credenciais pessoais de cliente (para armazenar os profiles de seu projeto em sua conta pessoal). Encontre-as no topo da página Settings/Credentials e execute o seguinte comando substituindo os espaços reservados:

1
$ blackfire config --client-id=xxx --client-token=xxx

Nota

Para obter instruções completas de instalação, siga o guia de instalação detalhada oficial. Elas são úteis ao instalar o Blackfire em um servidor.

Configurando o Agente do Blackfire no Docker

A última etapa é adicionar o serviço do agente do Blackfire na stack do Docker Compose:

patch_file
 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 se comunicar com o servidor, você precisa obter suas credenciais pessoais de servidor (essas credenciais identificam onde você deseja armazenar os profiles - você pode criar uma por projeto); elas podem ser encontradas na parte inferior da página Settings/Credentials. Armazene-as em um arquivo local .env.local:

1
2
BLACKFIRE_SERVER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BLACKFIRE_SERVER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Você agora pode iniciar o novo container:

1
2
$ docker-compose stop
$ docker-compose up -d

Corrigindo uma Instalação do Blackfire Que não Funciona

Se você receber um erro ao fazer profiles, aumente o nível de log do Blackfire para obter mais informações nos logs:

patch_file
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

Reinicie o servidor web:

1
2
$ symfony server:stop
$ symfony server:start -d

E verifique os logs mais recentes:

1
$ symfony server:log

Faça o profile novamente e verifique a saída nos logs.

Configurando o Blackfire em Produção

O Blackfire é incluído por padrão em todos os projetos da SymfonyCloud.

Configure as credenciais do servidor como variáveis de ambiente:

1
2
$ symfony var:set BLACKFIRE_SERVER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
$ symfony var:set BLACKFIRE_SERVER_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

E habilite a sonda PHP como qualquer outra extensão PHP:

patch_file
 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.3

 runtime:
     extensions:
+        - blackfire
         - xsl
         - amqp
         - redis

Configurando o Varnish para o Blackfire

Antes que você possa implantar para começar a fazer profiles, você precisa de uma maneira de ignorar o cache HTTP do Varnish. Caso contrário, o Blackfire nunca irá atingir a aplicação PHP. Você vai autorizar apenas requisições de profiles vindo da sua máquina local.

Encontre o seu endereço IP atual:

1
$ curl https://ifconfig.me/

E utilize-o para configurar o Varnish:

patch_file
 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 +14,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 {

Agora você pode implantar.

Fazendo Profiles de Páginas Web

Você pode fazer profiles de páginas web tradicionais a partir do Firefox ou do Google Chrome por meio de suas extensões dedicadas.

Em sua máquina local, não se esqueça de desabilitar o cache HTTP em public/index.php ao fazer profiles: caso contrário, você irá fazer o profile da camada de cache HTTP do Symfony em vez do seu próprio código:

patch_file
 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 ter uma ideia melhor do desempenho da sua aplicação em produção, você também deve fazer profiles do ambiente de “produção”. Por padrão, seu ambiente local usa o ambiente de “desenvolvimento”, que adiciona uma sobrecarga significativa (principalmente para coletar dados para a barra de ferramentas para depuração web e para o Profiler do Symfony).

Mudar a sua máquina local para o ambiente de produção pode ser feito alterando a variável de ambiente APP_ENV no arquivo .env.local:

1
APP_ENV=prod

Ou você pode usar o comando server:prod:

1
$ symfony server:prod

Não esqueça de mudar de volta para o ambiente de desenvolvimento quando você terminar de fazer profiles:

1
$ symfony server:prod --off

Fazendo Profiles de Recursos da API

É melhor fazer profiles da API ou do SPA pela CLI através da Ferramenta CLI do Blackfire que você instalou anteriormente:

1
$ blackfire curl `symfony var:export SYMFONY_DEFAULT_ROUTE_URL`api

O comando blackfire curl aceita exatamente os mesmos argumentos e opções que o cURL.

Comparando o Desempenho

Na etapa sobre “Cache”, adicionamos uma camada de cache para melhorar o desempenho do nosso código, mas não verificamos nem medimos o impacto desta mudança no desempenho. Como todos nós somos muito ruins em adivinhar o que será rápido e o que é lento, você pode acabar em uma situação na qual otimizar alguma coisa na verdade torna sua aplicação mais lenta.

Você sempre deve medir o impacto de qualquer otimização que você fizer com um profiler. O Blackfire torna isso mais fácil visualmente graças ao seu recurso de comparação.

Escrevendo Testes Funcionais Caixa Preta

Já vimos como escrever testes funcionais com o Symfony. O Blackfire pode ser usado para escrever cenários de navegação que podem ser executados sob demanda através do player do Blackfire. Vamos escrever um cenário onde um novo comentário é submetido e validado através do link de e-mail no ambiente de desenvolvimento e através do painel administrativo em produção.

Crie um arquivo .blackfire.yaml com o seguinte conteúdo:

.blackfire.yaml
 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/"

Baixe o player do Blackfire para poder executar o cenário localmente:

1
2
$ curl -OLsS https://get.blackfire.io/blackfire-player.phar
$ chmod +x blackfire-player.phar

Execute este cenário no ambiente de desenvolvimento:

1
$ ./blackfire-player.phar run --endpoint=`symfony var:export SYMFONY_DEFAULT_ROUTE_URL` .blackfire.yaml --variable "webmail_url=`symfony var:export MAILER_WEB_URL 2>/dev/null`" --variable="env=dev"

Ou em produção:

1
$ ./blackfire-player.phar run --endpoint=`symfony env:urls --first` .blackfire.yaml --variable "webmail_url=NONE" --variable="env=prod"

Os cenários do Blackfire podem também acionar profiles a cada requisição e executar testes de desempenho adicionando a flag --blackfire.

Automatizando Verificações de Desempenho

Gerenciar o desempenho não é apenas melhorar o desempenho do código existente, mas também verificar se nenhuma regressão de desempenho foi introduzida.

O cenário escrito na seção anterior pode ser executado automaticamente em um workflow de integração contínua ou periodicamente em produção.

Na SymfonyCloud, também é possível executar cenários sempre que você criar uma nova branch ou implantar em produção para verificar automaticamente o desempenho do novo código.


  • « Previous Passo 28: Localizando uma Aplicação
  • Next » Passo 30: Descobrindo o Funcionamento Interno do Symfony

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.