Medindo o desempenho com a sincronização do servidor
Publicados: 2022-03-10Ao realizar qualquer tipo de trabalho de otimização de desempenho, uma das primeiras coisas que aprendemos é que, antes de melhorar o desempenho, você deve primeiro medi-lo. Sem poder medir a velocidade com que algo está funcionando, não podemos dizer se as alterações feitas estão melhorando o desempenho, não surtindo efeito ou até piorando as coisas.
Muitos de nós estarão familiarizados com o trabalho em um problema de desempenho em algum nível. Isso pode ser algo tão simples quanto tentar descobrir por que o JavaScript em sua página não está funcionando em breve, ou por que as imagens estão demorando muito para aparecer no wifi ruim do hotel. A resposta para esse tipo de pergunta geralmente é encontrada em um lugar muito familiar: as ferramentas de desenvolvedor do seu navegador.
Ao longo dos anos, as ferramentas de desenvolvedor foram aprimoradas para nos ajudar a solucionar esses tipos de problemas de desempenho no front-end de nossos aplicativos. Os navegadores agora têm auditorias de desempenho integradas. Isso pode ajudar a rastrear problemas de front-end, mas essas auditorias podem mostrar outra fonte de lentidão que não podemos corrigir no navegador. Esse problema é o tempo de resposta do servidor lento.
“Tempo para o primeiro byte”
Há muito pouco que as otimizações do navegador possam fazer para melhorar uma página que é simplesmente lenta para construir no servidor. Esse custo é incorrido entre o navegador que faz a solicitação do arquivo e recebe a resposta. Estudar seu gráfico de cascata de rede nas ferramentas do desenvolvedor mostrará esse atraso na categoria "Aguardando (TTFB)". Este é o tempo que o navegador espera entre fazer a solicitação e receber a resposta.
Em termos de desempenho, isso é conhecido como Tempo até o primeiro byte - a quantidade de tempo que leva antes que o servidor comece a enviar algo com o qual o navegador possa começar a trabalhar. Englobado nesse tempo de espera está tudo o que o servidor precisa fazer para construir a página. Para um site típico, isso pode envolver rotear a solicitação para a parte correta do aplicativo, autenticar a solicitação, fazer várias chamadas para sistemas de back-end, como bancos de dados e assim por diante. Pode envolver a execução de conteúdo por meio de sistemas de modelagem, fazer chamadas de API para serviços de terceiros e talvez até coisas como enviar e-mails ou redimensionar imagens. Qualquer trabalho que o servidor faça para concluir uma solicitação é esmagado nessa espera TTFB que o usuário experimenta em seu navegador.
Então, como reduzimos esse tempo e começamos a entregar a página mais rapidamente ao usuário? Bem, essa é uma grande pergunta, e a resposta depende da sua aplicação. Esse é o trabalho de otimização de desempenho em si. O que precisamos fazer primeiro é medir o desempenho para que o benefício de qualquer mudança possa ser julgado.
O cabeçalho de tempo do servidor
O trabalho do Server Timing não é ajudá-lo a realmente cronometrar a atividade em seu servidor. Você precisará fazer o tempo usando qualquer conjunto de ferramentas que sua plataforma de back-end disponibilize para você. Em vez disso, o objetivo do Server Timing é especificar como essas medições podem ser comunicadas ao navegador.
A forma como isso é feito é muito simples, transparente para o usuário e tem um impacto mínimo no peso da sua página. As informações são enviadas como um conjunto simples de cabeçalhos de resposta HTTP.
Server-Timing: db;dur=123, tmpl;dur=56
Este exemplo comunica dois pontos de tempo diferentes chamados db
e tmpl
. Estes não fazem parte da especificação - estes são os nomes que escolhemos, neste caso para representar alguns tempos de banco de dados e modelos, respectivamente.
A propriedade dur
está informando o número de milissegundos que a operação levou para ser concluída. Se observarmos a solicitação na seção Rede das Ferramentas do desenvolvedor, podemos ver que os horários foram adicionados ao gráfico.
O cabeçalho Server-Timing
pode ter várias métricas separadas por vírgulas:
Server-Timing: metric, metric, metric
Cada métrica pode especificar três propriedades possíveis
- Um nome curto para a métrica (como
db
em nosso exemplo) - Uma duração em milissegundos (expressa como
dur=123
) - Uma descrição (expressa como
desc="My Description"
)
Cada propriedade é separada com um ponto e vírgula como delimitador. Poderíamos adicionar descrições ao nosso exemplo assim:
Server-Timing: db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing"
A única propriedade necessária é name
. Tanto dur
quanto desc
são opcionais e podem ser usados opcionalmente quando necessário. Por exemplo, se você precisar depurar um problema de tempo que estava acontecendo em um servidor ou data center e não em outro, pode ser útil adicionar essas informações à resposta sem um tempo associado.
Server-Timing: datacenter;desc="East coast data center", db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing”
Isso então apareceria junto com os horários.
Uma coisa que você pode notar é que as barras de tempo não aparecem em um padrão em cascata. Isso ocorre simplesmente porque o Server Timing não tenta comunicar a sequência de timings, apenas as próprias métricas brutas.
Implementando o tempo do servidor
A implementação exata em seu próprio aplicativo dependerá de sua circunstância específica, mas os princípios são os mesmos. Os passos serão sempre:
- Tempo algumas operações
- Reúna os resultados de tempo
- Saída do cabeçalho HTTP
Em pseudocódigo, a geração de resposta pode ser assim:
startTimer('db') getInfoFromDatabase() stopTimer('db') startTimer('geo') geolocatePostalAddressWithAPI('10 Downing Street, London, UK') endTimer('geo') outputHeader('Server-Timing', getTimerOutput())
O básico da implementação de algo nesse sentido deve ser direto em qualquer linguagem. Uma implementação PHP muito simples pode usar a função microtime()
para operações de temporização e pode parecer algo como o seguinte.
class Timers { private $timers = []; public function startTimer($name, $description = null) { $this->timers[$name] = [ 'start' => microtime(true), 'desc' => $description, ]; } public function endTimer($name) { $this->timers[$name]['end'] = microtime(true); } public function getTimers() { $metrics = []; if (count($this->timers)) { foreach($this->timers as $name => $timer) { $timeTaken = ($timer['end'] - $timer['start']) * 1000; $output = sprintf('%s;dur=%f', $name, $timeTaken); if ($timer['desc'] != null) { $output .= sprintf(';desc="%s"', addslashes($timer['desc'])); } $metrics[] = $output; } } return implode($metrics, ', '); } }
Um script de teste o usaria como abaixo, aqui usando a função usleep()
para criar artificialmente um atraso na execução do script para simular um processo que leva tempo para ser concluído.
$Timers = new Timers(); $Timers->startTimer('db'); usleep('200000'); $Timers->endTimer('db'); $Timers->startTimer('tpl', 'Templating'); usleep('300000'); $Timers->endTimer('tpl'); $Timers->startTimer('geo', 'Geocoding'); usleep('400000'); $Timers->endTimer('geo'); header('Server-Timing: '.$Timers->getTimers());
A execução deste código gerou um cabeçalho que se parecia com isso:
Server-Timing: db;dur=201.098919, tpl;dur=301.271915;desc="Templating", geo;dur=404.520988;desc="Geocoding"
Implementações existentes
Considerando o quão útil é o Server Timing, existem relativamente poucas implementações que eu poderia encontrar. O pacote NPM de sincronização do servidor oferece uma maneira conveniente de usar a sincronização do servidor a partir de projetos do Node.
Se você usa um framework PHP baseado em middleware, tuupola/server-timing-middleware também fornece uma opção útil. Estou usando isso em produção no Notist há alguns meses e sempre deixo alguns horários básicos ativados se você quiser ver um exemplo na natureza.
Para suporte ao navegador, o melhor que vi está no Chrome DevTools, e foi isso que usei para as capturas de tela deste artigo.
Considerações
O próprio Server Timing adiciona sobrecarga mínima à resposta HTTP enviada de volta pela rede. O cabeçalho é muito mínimo e geralmente é seguro enviar sem se preocupar em direcionar apenas para usuários internos. Mesmo assim, vale a pena manter nomes e descrições curtos para não adicionar sobrecarga desnecessária.
Mais preocupante é o trabalho extra que você pode estar fazendo no servidor para cronometrar sua página ou aplicativo. A adição de tempo extra e registro em log pode ter um impacto no desempenho, portanto, vale a pena implementar uma maneira de ativar e desativar isso quando necessário.
Usar um cabeçalho Server Timing é uma ótima maneira de garantir que todas as informações de tempo do front-end e do back-end do seu aplicativo estejam acessíveis em um único local. Desde que seu aplicativo não seja muito complexo, pode ser fácil de implementar e você pode estar pronto e funcionando em um período de tempo muito curto.
Se você quiser ler mais sobre o tempo do servidor, tente o seguinte:
- A especificação de tempo do servidor W3C
- A página MDN no Server Timing tem exemplos e detalhes atualizados de suporte ao navegador
- Um artigo interessante da equipe do BBC iPlayer sobre o uso do Server Timing.