Archive for the ‘Apache’ Tag

Otimizando um Servidor Web para Download Progressivo

No dia 27 do mês passado estive no Congresso SET, da Sociedade Brasileira de Engenharia de Televisão e Telecomunicações, onde apresentei junto com o Marcello Azambuja um artigo sobre Arquiteturas de Distribuição de Vídeos na Internet. Porém, devido ao tempo bastante limitado da apresentação, não pude entrar em detalhes mais específicos de como otimizar as arquiteturas para obter um melhor desempenho. Por isso, resolvi colocar um pouco do que está no artigo aqui no blog, já que nem todo mundo que tem interesse neste assunto estava na apresentação. (O slides podem ser vistos aqui)

Assim, resolvi começar falando especificamente sobre o Download Progressivo, ou seja, como podemos otimizar um Web Server para que ele seja capaz de servir a maior quantidade de usuários possível. No caso de distribuição de vídeos via Progressive Download temos algumas características básicas que devem ser consideradas para otimização:

  • As conexões com o WebServer são persistentes, ou seja, o usuário fica conectado ao servidor por uma quantidade razoável de tempo, até que o conteúdo seja completamente copiado;
  • Dependendo da quantidade de vídeos diferentes podemos ter diversos conteúdos diferentes sendo acessados simultaneamente;
  • Os arquivos de mídia não estão necessariamente nos discos do servidor. Eles podem estar em um repositório central sendo acessados pelo servidor Web através de NFS/CIFS;
  • A quantidade de acessos ao serviço pode aumentar rapidamente, variando de acordo com o conteúdo disponibilizado;

Com estas características já podemos identificar alguns gargalos principais:

  • Tráfego de rede: muitos usuários conectados realizando download irão rapidamente ocupar a banda disponível. Além disso, existe a ocupação de banda pela cópia dos arquivos do repositório central para o servidor;
  • IO (Repositório Central e Disco Local): muitos usuários realizando download significa muito IO de leitura, que é maior a medida que temos mais usuários acessando arquivos diferentes;
  • Memória: quanto mais usuários, mais memória o Apache irá precisar para atendê-los, e quanto mais arquivos, mais memória o kernel irá utilizar para cache;
  • CPU: quanto mais usuários mais processamento para organizar e servir as requisições;

Na verdade, se queremos obter o máximo de uma arquitetura não podemos nos limitar a olhar apenas um componente. O primeiro ponto, e mais simples, consiste em alterar as configurações do Apache, escolher a versão correta (2.X), e compilar uma versão do Web Server que tenha apenas aquilo que é relevante para a distribuição de arquivos, ou seja, devemos evitar incluir na configuração de compilação módulos que não serão utilizados, como mod ssl, por exemplo.

Uma vez compilado cabe a nós decidir qual arquitetura interna de funcionamento possui uma performance maior: se é a multi-process (Apache Prefork) ou a multi-process/multi-thread (Apache Worker).

Basicamente, no prefork, para cada conexão será criado um processo específico para atendê-la, de modo que a quantidade de processos httpd será sempre maior ou igual a quantidade de clientes. Nesta arquitetura, temos uma quantidade inicial que processos que são pré-criados para atender as conexões, sendo que a medida que os clientes se conectam, eles são atendidos por estes processos. No prefork, temos basicamente dois problemas em termos de performance:

  • Memória: cada processo criado ocupa uma porção da memória, de modo que a memória disponível para cache do kernel é cada vez menor, ao ponto que toda ela é preenchida pelos processos httpd, momento onde o servidor começa a fazer swap em disco;
  • Load: a criação de novos processos (fork) gera um overhead no sistema, de modo que quanto maior a taxa de conexão, maior será o overhead no fork para atender as novas conexões;

A outra opção de MPM, o worker, trabalha com o conceito multi-thread, onde as requisições são atendidas por threads e não por processos. Assim, teríamos apenas um ou poucos processos com múltiplas threads em cada um deles, atendendo cada uma das conexões. Com o worker, minimizamos a utlização de memória, já que as threads compartilham a mesma área de memória do processo pai, e reduzimos o overhead na criação de processos. Na verdade, no caso do worker, definimos a quantidade de threads por processo de modo que só realizamos um fork quando este limite é atingido.

A escolha entre prefork ou worker deve ser realizada caso a caso, ou seja, dependendo da situação de uso e da configuração de hardware uma escolha poderá ser melhor que a outra. No caso específico de servidores para utilização em distribuição de vídeos em Progressive Download, a escolha do MPM Worker é recomendada, com o objetivo de reduzir a ocupação de memória pelo servidor Web, deixando-a livre para que o kernel possa utilizá-la para cache de conteúdo, reduzindo o IO no disco e conseqüentemente o tempo de resposta da requisição. Uma configuração interessante seria neste caso seria forçar a criação de todos os processos filho (children) e suas threads no momento que o servidor Web for iniciado, reduzindo assim o overhead de controle de threads.

Além de configurar o MPM, o Apache permite diversas outras configurações capazes de aumentar o desempenho do mesmo, como a possibilidade de desabilitar Hostname Lookups e DNS resolving, e outras que podem ser encontradas na documentação do Apache.

Entretanto, quando falamos de arquiteturas de distribuição de vídeo que recebem grandes volumes de acesso, devemos nos preocupar um pouco mais em como otimizar o processo de distribuição. Uma das maneiras mais eficientes de aumentar a capacidade de uma infra-estrutura de Download Progressivo consiste em realizar um processo de cache intensivo. Ter uma estratégia de cache de conteúdo é fundamental quando falamos de alta performance com um grande volume de acessos. Quando temos um volume muito grande, temos também diversos requests ao mesmo conteúdo. Processar todo o request para cada cliente consistiria em repetir as mesmas operações diversas vezes, sem aproveitar para um request os dados processados por outro. Assim, fica claro que podemos otimizar este processamento, simplesmente aproveitando aquilo que serve para mais de uma requisição.

Em situações onde temos uma arquitetura onde o Apache funciona como uma espécie de “proxy” entre o usuário e um repositório central de vídeos, essa necessidade é ainda mais latente, uma vez que não faz sentido obter um mesmo arquivo diversas vezes neste repositório para servir para os clientes a cada request semelhante. Uma vez copiado do storage para o Apache, para servir uma requisição, o arquivo pode ser mantido no servidor Web para as requisições posteriores realizadas ao mesmo vídeo. Com essa abordagem, reduzimos a carga no storage e o tráfego de rede e de operações de cópia.

Para realizar o cache de conteúdo existem diversas alternativas, como o Squid e o mod_cache. O mod_cache, por estar integrado ao Apache e por ser bastante conhecido e utilizado, além de apresentar uma ótima performance, é uma excelente opção para cache em arquiteturas de distribuição em Download Progressivo.

O mod_cache permite duas abordagens principais de cache: disco ou memória. O mod_disk_cache é o módulo responsável pelo cache em disco, e funciona da seguinte forma: ao receber um request, o mod_disk_cache verifica se o conteúdo solicitado já está no cache em disco. Se estiver, o módulo valida se o conteúdo não está expirado e serve o mesmo a partir do cache, sem que a requisição seja passada aos demais módulos do Apache. Caso, não esteja cacheado, o request passa pelo path normal de atendimento ao request, sendo que, ao finalizar o processamento, o mod_cache escreve a resposta no cache antes de servir a mesma. Assim, ele cria dois arquivos em disco: o .data, com o conteúdo do request, e o .header com os headers da resposta. O nome dos arquivos criados é criado a partir de um hash, para evitar que o conteúdo seja sobre-escrito.

O mod_mem_cache, é o módulo responsável pelo cache em memória, e funciona de forma semelhante ao mod_disk_cache, porém armazenando o conteúdo em memória.

A escolha de um dos módulos para utilização em um servidor de Download Progressivo também depende da configuração do hardware. Entretanto, em servidores que rodam em Linux, o uso do mod_disk_cache é recomendada, uma vez que o gerenciamento do cache em memória feito pelo kernel é bastante eficiente.

Com um web server bem otimizado e configurado, e com uma estratégia de cache eficiente, temos uma arquitetura de distribuição muito mais robusta e capaz de atender um volume até 70% maior que o volume que atenderíamos utilizando apenas um servidor com as configurações “default”, o que reduz de forma significativa os custos de investimento para expansão da capacidade, tornando esta uma opção bastante interessante para distribuição de vídeos na internet.

Advertisements

Otimizando a performance com diferentes estratégias de cache

De todas as maneiras existentes de se otimizar a performance de um software, acredito que a utilização de um cache intensivo é aquela que produz os melhores resultados, principalmente quando falamos de aplicações web. A utilização de uma estrutura de cache eficiente, aumenta significativamente a capacidade de processamento de requisições, uma vez que reduzimos a pressão sobre os gargalos da aplicação, sejam eles relacionados ao IO em disco, a utilização de memória ou a utilização de CPU. Para cada tipo de gargalo, e para cada tipo de aplicação, podemos utilizar uma estratégia de cache diferente, atuando diretamente nos pontos que limitam o desempenho do sistema.

Em uma aplicação web, temos três tipos básicos de cache: o cache no cliente, o cache no web server e o cache na aplicação. O cache no cliente é o método mais simples de redução de carga nos servidores web, principalmente quando falamos de sites simples, e com grande quantidade de conteúdo estático. Imagine, por exemplo, um site que possui muitas imagens e textos estáticos. Se não utilizarmos nenhuma política de cache, a cada request realizado, todos os componentes serão enviados do servidor para o cliente, mesmo que este cliente já tenha visualizado a página diversas vezes. Todas as imagens que já foram transferidas para ele, e que não sofreram alteração, serão novamente enviadas, algo que poderia ser facilmente evitado se o cliente tivesse armazenado previamente este conteúdo estático. Para que o cliente armazene este conteúdo, o servidor deve mandar, juntamente com a resposta, a informação de que aquele conteúdo deve ser cacheado, e por quanto tempo ele será válido no cache. Para isto, existem dois módulos do Apache que permitem uma excelente flexibilidade na definição de qual conteúdo será cacheado e o tempo de expiração do mesmo. São eles: mod_headers e mod_expires.

ExpiresByType text/html “access plus 2 hours”
ExpiresByType image/gif “modification plus 3 minutes”

Algumas vezes, apesar de termos conteúdo estático, a utilização de cache no cliente não faz muito sentido. Imaginem um site de download de músicas, onde temos diversos servidores web servindo conteúdo estático a partir de um repositório central de arquivos (um storage comum). Se vários usuários diferentes tentam realizar o download de um determinado arquivo, a cada requisição o web server irá ler o mesmo arquivo do storage central, gerando uma carga desnecessária no backend. O mesmo pode ser aplicado se no backend temos um servidor de aplicação, onde o conteúdo gerado se altera com pouca freqüência ou com uma periodicidade definida. Neste caso, devemos utilizar uma estratégia de cache no web server, seja no próprio servidor, seja criando uma camada de cache entre ele e o usuário.

Existem diversos modos de se implementar um cache no servidor web, seja utilizando um Squid na frente, seja utilizando um appliance dedicado. Porém, a maneira que eu acho mais eficiente é utilizando o mod_cache. O mod_cache é um módulo do Apache específico para cache no web server, capaz de armazenar conteúdo em disco ou em memória, de acordo com as necessidades da aplicação. Por estar integrado ao Apache, sua configuração é extremamente simples e não existem problemas de compatibilidade, etc. Além disso, ele é bastante confiável (quando utilizado com o Apache 2.2.x) e é Open-Source, sendo suportado por uma comunidade bastante ativa. (para quem quiser saber mais sobre o mod_cache).

Em algumas situações específicas estas duas abordagens de cache não são suficientes, sendo necessário a utilização de uma terceira: o cache da aplicação. Existem casos onde a camada de aplicação necessita de consultar serviços remotos, ou mesmo bancos de dados, para gerar uma determinada saída. Nestes casos, seria razoável que fosse armazenada a resposta de uma determinada consulta, desde que ela possa ser reaproveitada para o processamento de outras requisições. Desta forma, aumentamos a velocidade de resposta, o que bastante interessante do ponto de vista do usuário. Assim, para realizar este tipo de cache, podemos, por exemplo, utilizar o memcached, que é uma excelente ferramenta de cache de dados em memória.

Em suma, temos diferentes tipos de cache para diferentes tipo de problemas, sendo que a utilização de uma política de cache correta garante um ganho substancial de desempenho (podemos ter ganhos de até 70%). Quanto mais intensiva e bem estruturada for a utilização de cache, melhor será a utilização dos recursos para o que realmente importa e menor será o tempo de resposta para o usuário.