BLOG | NGINX

Os pools de threads no NGINX aumentam o desempenho em 9x!

NGINX-Parte-de-F5-horiz-preto-tipo-RGB
Valentin Bartenev Miniatura
Valentin Bartenev
Publicado em 19 de junho de 2015

É bem sabido que o NGINX usa uma abordagem assíncrona e orientada a eventos para lidar com conexões . Isso significa que, em vez de criar outro processo ou thread dedicado para cada solicitação (como servidores com arquitetura tradicional), ele manipula várias conexões e solicitações em um processo de trabalho. Para conseguir isso, o NGINX trabalha com soquetes em um modo não bloqueante e usa métodos eficientes como epoll e kqueue .

Como o número de processos de peso total é pequeno (geralmente apenas um por núcleo da CPU) e constante, muito menos memória é consumida e os ciclos da CPU não são desperdiçados na troca de tarefas. As vantagens dessa abordagem são bem conhecidas pelo exemplo do próprio NGINX. Ele lida com sucesso com milhões de solicitações simultâneas e é muito bem dimensionado.

Cada processo consome memória adicional e cada troca entre eles consome ciclos de CPU e destrói L-caches

Mas a abordagem assíncrona e orientada a eventos ainda tem um problema. Ou, como eu gosto de pensar, um “inimigo”. E o nome do inimigo é: bloqueio . Infelizmente, muitos módulos de terceiros usam chamadas de bloqueio, e os usuários (e às vezes até mesmo os desenvolvedores dos módulos) não estão cientes das desvantagens. Operações de bloqueio podem prejudicar o desempenho do NGINX e devem ser evitadas a todo custo.

Mesmo no código oficial atual do NGINX não é possível evitar operações de bloqueio em todos os casos e, para resolver esse problema, o novo mecanismo de “thread pools” foi implementado no NGINX versão 1.7.11 e no NGINX Plus Release 7 . O que é e como deve ser usado, abordaremos mais tarde. Agora vamos ficar cara a cara com nosso inimigo.

Editor – Para uma visão geral do NGINX Plus R7, consulte Anunciando o NGINX Plus R7 em nosso blog.

Para discussões detalhadas sobre outros novos recursos no NGINX Plus R7, consulte estas postagens de blog relacionadas:

 

O Problema

Primeiro, para melhor compreensão do problema, algumas palavras sobre como o NGINX funciona.

Em geral, o NGINX é um manipulador de eventos, um controlador que recebe informações do kernel sobre todos os eventos que ocorrem nas conexões e então fornece comandos ao sistema operacional sobre o que fazer. Na verdade, o NGINX faz todo o trabalho pesado orquestrando o sistema operacional, enquanto o sistema operacional faz o trabalho rotineiro de ler e enviar bytes. Portanto, é muito importante que o NGINX responda de forma rápida e oportuna.

NGINX-Evento-Loop2
O processo de trabalho escuta e processa eventos do kernel

Os eventos podem ser tempos limite, notificações sobre soquetes prontos para leitura ou gravação ou notificações sobre um erro ocorrido. O NGINX recebe vários eventos e os processa um por um, executando as ações necessárias. Assim, todo o processamento é feito em um loop simples em uma fila em um thread. O NGINX retira um evento da fila e reage a ele, por exemplo, gravando ou lendo um soquete. Na maioria dos casos, isso é extremamente rápido (talvez exigindo apenas alguns ciclos de CPU para copiar alguns dados na memória) e o NGINX prossegue por todos os eventos na fila em um instante.

Ciclo de processamento da fila de eventos
Todo o processamento é feito em um loop simples por um thread

Mas o que acontecerá se alguma operação longa e pesada tiver ocorrido? Todo o ciclo de processamento de eventos ficará preso esperando a conclusão desta operação.

Então, ao dizer “uma operação de bloqueio” queremos dizer qualquer operação que interrompa o ciclo de manipulação de eventos por um período de tempo significativo. As operações podem estar bloqueadas por vários motivos. Por exemplo, o NGINX pode estar ocupado com um processamento longo e intensivo de CPU, ou pode ter que esperar para acessar um recurso (como um disco rígido, ou uma chamada de função de biblioteca ou mutex que obtém respostas de um banco de dados de forma síncrona, etc.). O ponto principal é que, ao processar essas operações, o processo de trabalho não pode fazer mais nada e não pode manipular outros eventos, mesmo que haja mais recursos do sistema disponíveis e alguns eventos na fila possam utilizar esses recursos.

Imagine um vendedor em uma loja com uma longa fila na sua frente. O primeiro cara da fila pede algo que não está na loja, mas está no depósito. O vendedor vai até o depósito para entregar as mercadorias. Agora, toda a fila precisa esperar algumas horas por essa entrega e todos na fila ficam insatisfeitos. Você consegue imaginar a reação das pessoas? O tempo de espera de cada pessoa na fila aumenta com essas horas, mas os itens que elas pretendem comprar podem estar ali mesmo na loja.

Todos na fila têm que esperar pelo pedido da primeira pessoa

Quase a mesma situação acontece com o NGINX quando ele pede para ler um arquivo que não está armazenado em cache na memória, mas precisa ser lido do disco. Os discos rígidos são lentos (especialmente os que estão girando) e, embora as outras solicitações na fila possam não precisar de acesso à unidade, elas são forçadas a esperar de qualquer maneira. Como resultado, as latências aumentam e os recursos do sistema não são totalmente utilizados.

Apenas uma operação de bloqueio pode atrasar todas as operações seguintes por um tempo significativo

Alguns sistemas operacionais fornecem uma interface assíncrona para leitura e envio de arquivos e o NGINX pode usar essa interface (veja a diretiva aio ). Um bom exemplo aqui é o FreeBSD. Infelizmente, não podemos dizer o mesmo sobre o Linux. Embora o Linux forneça um tipo de interface assíncrona para leitura de arquivos, ele tem algumas desvantagens significativas. Um deles são os requisitos de alinhamento para acesso a arquivos e buffers, mas o NGINX lida bem com isso. Mas o segundo problema é pior. A interface assíncrona requer que o sinalizador O_DIRECT seja definido no descritor de arquivo, o que significa que qualquer acesso ao arquivo ignorará o cache na memória e aumentará a carga nos discos rígidos. Isso definitivamente não o torna ideal para muitos casos.

Para resolver esse problema em particular, os pools de threads foram introduzidos no NGINX 1.7.11 e no NGINX Plus Release 7.

Agora vamos mergulhar no que são os pools de threads e como eles funcionam.

Pools de Tópicos

Vamos voltar ao nosso pobre assistente de vendas que entrega mercadorias de um armazém distante. Mas ele ficou mais esperto (ou talvez tenha ficado mais esperto depois de ser espancado pela multidão de clientes furiosos?) e contratou um serviço de entrega. Agora, quando alguém pede algo de um depósito distante, em vez de ir até o depósito, ele simplesmente deixa o pedido em um serviço de entrega e eles cuidam do pedido enquanto nosso assistente de vendas continua atendendo outros clientes. Assim, apenas os clientes cujas mercadorias não estão na loja aguardam a entrega, enquanto os demais podem ser atendidos imediatamente.

Passar um pedido para o serviço de entrega desbloqueia a fila

Em termos de NGINX, o pool de threads está executando as funções do serviço de entrega. Ele consiste em uma fila de tarefas e um número de threads que manipulam a fila. Quando um processo de trabalho precisa fazer uma operação potencialmente longa, em vez de processar a operação sozinho, ele coloca uma tarefa na fila do pool, da qual ela pode ser retirada e processada por qualquer thread livre.

Os pools de threads ajudam a aumentar o desempenho do aplicativo atribuindo uma operação lenta a um conjunto separado de tarefas
O processo de trabalho descarrega operações de bloqueio para o pool de threads

Parece que temos outra fila. Certo. Mas neste caso a fila é limitada por um recurso específico. Não podemos ler de uma unidade mais rápido do que a unidade é capaz de produzir dados. Agora, pelo menos, a unidade não atrasa o processamento de outros eventos e apenas as solicitações que precisam acessar os arquivos ficam esperando.

A operação de “leitura do disco” é frequentemente usada como o exemplo mais comum de uma operação de bloqueio, mas, na verdade, a implementação de pools de threads no NGINX pode ser usada para quaisquer tarefas que não sejam apropriadas para processar no ciclo de trabalho principal.

No momento, o descarregamento para pools de threads é implementado apenas para três operações essenciais: a chamada de sistema read() na maioria dos sistemas operacionais, sendfile() no Linux e aio_write() no Linux, que é usada ao gravar alguns arquivos temporários, como os do cache. Continuaremos testando e comparando a implementação e poderemos transferir outras operações para os pools de threads em versões futuras se houver um benefício claro.

Editor – O suporte para a chamada de sistema aio_write() foi adicionado no NGINX 1.9.13 e no NGINX Plus R9 .

Avaliação comparativa

É hora de passar da teoria para a prática. Para demonstrar o efeito do uso de pools de threads, realizaremos um benchmark sintético que simula a pior combinação de operações de bloqueio e não bloqueio.

Ele requer um conjunto de dados que certamente não caberá na memória. Em uma máquina com 48 GB de RAM, geramos 256 GB de dados aleatórios em arquivos de 4 MB e então configuramos o NGINX 1.9.0 para atendê-los.

A configuração é bem simples:

worker_processes 16;
events {
accept_mutex off;
}

http {
include mime.types;
default_type application/octet-stream;

access_log off;
sendfile on;
sendfile_max_chunk 512k;

server {
listen 8000;

location / {
root /storage;
}
}
}

Como você pode ver, para obter melhor desempenho, alguns ajustes foram feitos: logging e accept_mutex foram desabilitados, sendfile foi habilitado e sendfile_max_chunk foi definido. A última diretiva pode reduzir o tempo máximo gasto no bloqueio de chamadas sendfile() , já que o NGINX não tentará enviar o arquivo inteiro de uma vez, mas o fará em blocos de 512 KB.

A máquina tem dois processadores Intel Xeon E5645 (12 núcleos, 24 HT‑threads no total) e uma interface de rede de 10‑Gbps. O subsistema de disco é representado por quatro discos rígidos Western Digital WD1003FBYX dispostos em uma matriz RAID10. Todo esse hardware é alimentado pelo Ubuntu Server 14.04.1 LTS.

Configuração de geradores de carga e NGINX para o benchmark

Os clientes são representados por duas máquinas com as mesmas especificações. Em uma dessas máquinas, o wrk cria carga usando um script Lua. O script solicita arquivos do nosso servidor em uma ordem aleatória usando 200 conexões paralelas, e cada solicitação provavelmente resultará em uma perda de cache e um bloqueio de leitura do disco. Vamos chamar essa carga de carga aleatória .

Na segunda máquina cliente, executaremos outra cópia do wrk que solicitará o mesmo arquivo várias vezes usando 50 conexões paralelas. Como esse arquivo será acessado com frequência, ele permanecerá na memória o tempo todo. Em circunstâncias normais, o NGINX atenderia a essas solicitações muito rapidamente, mas o desempenho cairia se os processos de trabalho fossem bloqueados por outras solicitações. Vamos chamar essa carga de carga constante .

O desempenho será medido monitorando a taxa de transferência da máquina servidora usando ifstat e obtendo resultados de trabalho do segundo cliente.

Agora, a primeira execução sem pools de threads não nos dá resultados muito interessantes:

% ifstat -bi eth2 eth2 Kbps de entrada Kbps de saída 5531,24 1,03e+06 4855,23 812922,7 5994,66 1,07e+06 5476,27 981529,3 6353,62 1,12e+06 5166,17 892770,3 5522,81 978540,8 6208,10 985466,7 6370,79 1,12e+06 6123,33 1,07e+06

Como você pode ver, com essa configuração o servidor é capaz de produzir cerca de 1 Gbps de tráfego no total. Na saída de top , podemos ver que todos os processos de trabalho passam a maior parte do tempo em bloqueio de E/S (eles estão em um estado D ):

topo - 10:40:47 up 11 dias, 1:32, 1 usuário, média de carga: 49,61, 45,77 62,89Tarefas:375 total,2 correndo,373 dormindo,0 parou,0 zumbi %Cpu(s):0.0 nós,0.3 sim,0.0 não,67.7 eu ia,31.9 o quê,0.0 oi,0.0 si,0.0 º KiB Mem:49453440 total,49149308 usado,304132 livre,98780 buffers Troca de KiB:10474236 total,20124 usado,10454112 livre,46903412 cached Mem PID USUÁRIO PR NI VIRT RES SHR S %CPU %MEM TIME+ COMANDO 4639 vbart 20 0 47180 28152 496 D 0,7 0,1 0:00.17 nginx 4632 vbart 20 0 47180 28196 536 D 0,3 0,1 0:00.11 nginx 4633 vbart 20 0 47180 28324 540 D 0,3 0,1 0:00.11 nginx 4635 vbart 20 0 47180 28136 480 D 0,3 0,1 0:00.12 nginx 4636 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00.14 nginx 4637 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00.10 nginx 4638 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00.12 nginx 4640 vbart 20 0 47180 28324 540 D 0,3 0,1 0:00.13 nginx 4641 vbart 20 0 47180 28324 540 D 0,3 0,1 0:00.13 nginx 4642 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00.11 nginx 4643 vbart 20 0 47180 28276 536 D 0,3 0,1 0:00.29 nginx 4644 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00.11 nginx 4645 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00.17 nginx 4646 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00.12 nginx 4647 vbart 20 0 47180 28208 532 D 0,3 0,1 0:00.17 nginx 4631 vbart 20 0 47180 756 252 S 0,0 0,1 0:00.00 nginx 4634 vbart 20 0 47180 28208 536 D 0,0 0,1 0:00.11 nginx< 4648 vbart 20 0 25232 1956 1160 R 0,0 0,0 0:00.08 topo 25921 vbart 20 0 121956 2232 1056 S 0,0 0,0 0:01.97 sshd 25923 vbart 20 0 40304 4160 2208 S 0,0 0,0 0:00,53 zsh

Nesse caso, a taxa de transferência é limitada pelo subsistema de disco, enquanto a CPU fica ociosa na maior parte do tempo. Os resultados do trabalho também são muito baixos:

Executando teste de 1m em http://192.0.2.1:8000/1/1/1 12 threads e 50 conexões
Estatísticas de Thread Desvio Padrão Médio Máx. +/- Desvio Padrão
Latência 7,42s 5,31s 24,41s 74,73%
Req/Seg 0,15 0,36 1,00 84,62%
488 solicitações em 1,01m, 2,01GB de leitura
Solicitações/seg:      8.08
Transferência/seg:     34,07 MB

E lembre-se, isso é para o arquivo que deve ser servido de memória! As latências excessivamente grandes ocorrem porque todos os processos de trabalho estão ocupados lendo arquivos das unidades para atender à carga aleatória criada por 200 conexões do primeiro cliente e não conseguem lidar com nossas solicitações em tempo hábil.

É hora de colocar nossos pools de threads em jogo. Para isso, basta adicionar a diretiva aio threads ao bloco location :

localização / { raiz / armazenamento;
threads aio;
}

e peça ao NGINX para recarregar sua configuração.

Depois disso repetimos o teste:

% ifstat -bi eth2 eth2 Kbps de entrada Kbps de saída 60915,19 9,51e+06 59978,89 9,51e+06 60122,38 9,51e+06 61179,06 9,51e+06 61798,40 9,51e+06 57072,97 9,50e+06 56072,61 9,51e+06 61279,63 9,51e+06 61243,54 9,51e+06 59632,50 9,50e+06

Agora nosso servidor produz 9,5 Gbps , comparado a ~1 Gbps sem pools de threads!

Provavelmente poderia produzir ainda mais, mas já atingiu a capacidade máxima prática da rede, então neste teste o NGINX é limitado pela interface de rede. Os processos de trabalho passam a maior parte do tempo apenas dormindo e esperando por novos eventos (eles estão no estado S no topo ):

topo - 10:43:17 up 11 dias, 1:35, 1 usuário, média de carga: 172,71, 93,84, 77,90Tarefas:376 total,1 correndo,375 dormindo,0 parou,0 zumbi %Cpu(s):0.2 nós,1.2 sim,0.0 não,34.8 eu ia,61.5 o quê,0.0 oi,2.3 si,0.0 º KiB Mem:49453440 total,49096836 usado,356604 livre,97236 buffers Troca de KiB:10474236 total,22860 usado,10451376 livre,46836580 cached Mem PID USUÁRIO PR NI VIRT RES SHR S %CPU %MEM TIME+ COMANDO 4654 vbart 20 0 309708 28844 596 S 9,0 0,1 0:08,65 nginx 4660 vbart 20 0 309748 28920 596 S 6,6 0,1 0:14,82 nginx 4658 vbart 20 0 309452 28424 520 S 4,3 0,1 0:01,40 nginx 4663 vbart 20 0 309452 28476 572 S 4,3 0,1 0:01,32 nginx 4667 vbart 20 0 309584 28712 588 S 3,7 0,1 0:05,19 nginx 4656 vbart 20 0 309452 28476 572 S 3,3 0,1 0:01,84 nginx 4664 vbart 20 0 309452 28428 524 S 3,3 0,1 0:01,29 nginx 4652 vbart 20 0 309452 28476 572 S 3,0 0,1 0:01,46 nginx 4662 vbart 20 0 309552 28700 596 S 2,7 0,1 0:05,92 nginx 4661 vbart 20 0 309464 28636 596 S 2,3 0,1 0:01,59 nginx 4653 vbart 20 0 309452 28476 572 S 1,7 0,1 0:01,70 nginx 4666 vbart 20 0 309452 28428 524 S 1,3 0,1 0:01,63 nginx 4657 vbart 20 0 309584 28696 592 S 1,0 0,1 0:00,64 nginx 4655 vbart 20 0 30958 28476 572 S 0,7 0,1 0:02,81 nginx 4659 vbart 20 0 309452 28468 564 S 0,3 0,1 0:01,20 nginx 4665 vbart 20 0 309452 28476 572 S 0,3 0,1 0:00,71 nginx 5180 vbart 20 0 25232 1952 1156 R 0,0 0,0 0:00,45 topo 4651 vbart 20 0 20032 752 252 S 0,0 0,0 0:00,00 nginx 25921 vbart 20 0 121956 2176 1000 S 0,0 0,0 0:01,98 sshd 25923 vbart 20 0 40304 3840 2208 S 0,0 0,0 0:00,54 zsh

Ainda há muitos recursos de CPU.

Os resultados do trabalho :

Executando teste de 1m em http://192.0.2.1:8000/1/1/1 12 threads e 50 conexões
Estatísticas de Thread Desvio Padrão Médio Máx. +/- Desvio Padrão
Latência 226,32ms 392,76ms 1,72s 93,48%
Req/Seg 20,02 10,84 59,00 65,91%
15045 solicitações em 1,00m, 58,86GB de leitura
Solicitações/seg:    250,57
Transferência/seg:      0.98 GB

O tempo médio para atender um arquivo de 4 MB foi reduzido de 7,42 segundos para 226,32 milissegundos (33 vezes menos), e o número de solicitações por segundo aumentou em 31 vezes (250 contra 8)!

A explicação é que nossas solicitações não esperam mais na fila de eventos para processamento enquanto os processos de trabalho são bloqueados na leitura, mas são manipuladas por threads livres. Enquanto o subsistema de disco estiver fazendo seu trabalho da melhor maneira possível, atendendo à nossa carga aleatória da primeira máquina cliente, o NGINX usa o restante dos recursos da CPU e a capacidade da rede para atender às solicitações do segundo cliente da memória.

Ainda não é uma solução mágica

Depois de todos os nossos medos sobre operações de bloqueio e alguns resultados interessantes, provavelmente a maioria de vocês já vai configurar pools de threads em seus servidores. Não tenha pressa.

A verdade é que, felizmente, a maioria das operações de leitura e envio de arquivos não lida com discos rígidos lentos. Se você tiver RAM suficiente para armazenar o conjunto de dados, um sistema operacional será inteligente o suficiente para armazenar em cache os arquivos usados com frequência em um chamado “cache de página”.

O cache de página funciona muito bem e permite que o NGINX demonstre ótimo desempenho em quase todos os casos de uso comuns. A leitura do cache da página é bastante rápida e ninguém pode chamar essas operações de “bloqueio”. Por outro lado, o descarregamento para um pool de threads tem alguma sobrecarga.

Então, se você tem uma quantidade razoável de RAM e seu conjunto de dados de trabalho não é muito grande, o NGINX já funciona da maneira mais otimizada sem usar pools de threads.

Descarregar operações de leitura para o pool de threads é uma técnica aplicável a tarefas muito específicas. É mais útil quando o volume de conteúdo solicitado com frequência não cabe no cache da VM do sistema operacional. Este pode ser o caso, por exemplo, de um servidor de streaming de mídia baseado em NGINX muito carregado. Esta é a situação que simulamos em nosso benchmark.

Seria ótimo se pudéssemos melhorar o descarregamento de operações de leitura em pools de threads. Tudo o que precisamos é de uma maneira eficiente de saber se os dados do arquivo necessários estão na memória ou não, e somente neste último caso a operação de leitura deve ser transferida para um thread separado.

Voltando à nossa analogia de vendas, atualmente o vendedor não pode saber se o item solicitado está na loja e deve sempre passar todos os pedidos para o serviço de entrega ou sempre lidar com eles ele mesmo.

O culpado é que os sistemas operacionais não têm esse recurso. As primeiras tentativas de adicioná-lo ao Linux como a chamada de sistema fincore() foram em 2010, mas isso não aconteceu. Mais tarde, houve uma série de tentativas de implementá-lo como uma nova chamada de sistema preadv2() com o sinalizador RWF_NONBLOCK (consulte Operações de leitura de arquivo com buffer não bloqueante e Operações de leitura com buffer assíncrono em LWN.net para obter detalhes). O destino de todos esses patches ainda não está claro. O ponto triste aqui é que parece que a principal razão pela qual esses patches ainda não foram aceitos no kernel é o contínuo bikeshedding .

Por outro lado, os usuários do FreeBSD não precisam se preocupar. O FreeBSD já tem uma interface assíncrona suficientemente boa para leitura de arquivos, que você deve usar em vez de pools de threads.

Configurando pools de threads

Então, se você tem certeza de que pode obter algum benefício usando pools de threads em seu caso de uso, então é hora de mergulhar fundo na configuração.

A configuração é bastante fácil e flexível. A primeira coisa que você deve ter é o NGINX versão 1.7.11 ou posterior, compilado com o argumento --with-threads no comando configure . Os usuários do NGINX Plus precisam da versão 7 ou posterior. No caso mais simples, a configuração parece muito simples. Tudo o que você precisa é incluir a diretiva aio threads no contexto apropriado:

# no contexto 'http', 'servidor' ou 'local' threads;

Esta é a configuração mínima possível de pools de threads. Na verdade, é uma versão curta da seguinte configuração:

# no contexto 'principal' thread_pool threads padrão=32 max_queue=65536;

# no contexto 'http', 'servidor' ou 'localização'
aio threads=default;

Ele define um pool de threads chamado default com 32 threads de trabalho e um comprimento máximo para a fila de tarefas de 65536 tarefas. Se a fila de tarefas estiver sobrecarregada, o NGINX rejeitará a solicitação e registrará este erro:

estouro de fila do pool de threads " NAME ": N tarefas em espera

O erro significa que é possível que os threads não consigam lidar com o trabalho tão rapidamente quanto ele é adicionado à fila. Você pode tentar aumentar o tamanho máximo da fila, mas se isso não ajudar, isso indica que seu sistema não é capaz de atender a tantas solicitações.

Como você já percebeu, com a diretiva thread_pool você pode configurar o número de threads, o comprimento máximo da fila e o nome de um pool de threads específico. O último implica que você pode configurar vários pools de threads independentes e usá-los em diferentes lugares do seu arquivo de configuração para atender a diferentes propósitos:

# no contexto 'principal'
thread_pool um threads=128 max_queue=0;
thread_pool dois threads=32;

http {
server {
location /one {
aio threads=one;
}

location /two {
aio threads=two;
}

}
# ...
}

Se o parâmetro max_queue não for especificado, o valor 65536 será usado por padrão. Como mostrado, é possível definir max_queue como zero. Nesse caso, o pool de threads só poderá manipular tantas tarefas quantos forem os threads configurados; nenhuma tarefa ficará na fila.

Agora vamos imaginar que você tem um servidor com três discos rígidos e quer que esse servidor funcione como um “proxy de cache” que armazena em cache todas as respostas dos seus backends. A quantidade esperada de dados armazenados em cache excede em muito a RAM disponível. Na verdade, é um nó de cache para seu CDN pessoal. É claro que neste caso o mais importante é obter o máximo desempenho dos drives.

Uma das opções é configurar um array RAID. Essa abordagem tem seus prós e contras. Agora com o NGINX você pode tirar mais uma:

# Assumimos que cada um dos discos rígidos está montado em um destes diretórios:# /mnt/disk1, /mnt/disk2 ou /mnt/disk3

# no contexto 'main'
thread_pool pool_1 threads=16;
thread_pool pool_2 threads=16;
thread_pool pool_3 threads=16;

http {
proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G 
use_temp_path=off;
proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G 
use_temp_path=off;
proxy_cache_path /mnt/disk3 níveis=1:2 keys_zone=cache_3:256m max_size=1024G 
use_temp_path=off;

split_clients $request_uri $disk {
33,3% 1;
33,3% 2;
* 3;
}

server {
# ...
location / {
proxy_pass http://backend;
proxy_cache_key $request_uri;
proxy_cache cache_$disk;
aio threads=pool_$disk;
sendfile on;
}
}
}

Nessa configuração, as diretivas thread_pool definem um pool de threads dedicado e independente para cada disco, e as diretivas proxy_cache_path definem um cache dedicado e independente em cada disco.

O módulo split_clients é usado para balanceamento de carga entre os caches (e, consequentemente, entre os discos), o que se encaixa perfeitamente nessa tarefa.

O parâmetro use_temp_path=off da diretiva proxy_cache_path instrui o NGINX a salvar arquivos temporários nos mesmos diretórios onde os dados de cache correspondentes estão localizados. É necessário evitar a cópia de dados de resposta entre os discos rígidos ao atualizar nossos caches.

Tudo isso junto nos permite obter o máximo desempenho do subsistema de disco atual, porque o NGINX, por meio de pools de threads separados, interage com as unidades em paralelo e de forma independente. Cada uma das unidades é atendida por 16 threads independentes com uma fila de tarefas dedicada para leitura e envio de arquivos.

Aposto que seus clientes gostam dessa abordagem personalizada. Certifique-se de que seus discos rígidos também gostem.

Este exemplo é uma boa demonstração de quão flexível o NGINX pode ser ajustado especificamente para seu hardware. É como se você estivesse dando instruções ao NGINX sobre a melhor maneira de interagir com a máquina e seu conjunto de dados. E ao ajustar o NGINX no espaço do usuário, você pode garantir que seu software, sistema operacional e hardware trabalhem juntos no modo mais otimizado para utilizar todos os recursos do sistema da forma mais eficaz possível.

Conclusão

Resumindo, os pools de threads são um ótimo recurso que leva o NGINX a novos níveis de desempenho ao eliminar um de seus inimigos mais conhecidos e antigos: o bloqueio, especialmente quando falamos de grandes volumes de conteúdo.

E ainda há mais por vir. Como mencionado anteriormente, esta nova interface permite potencialmente o descarregamento de qualquer operação longa e bloqueadora sem qualquer perda de desempenho. O NGINX abre novos horizontes em termos de ter uma massa de novos módulos e funcionalidades. Muitas bibliotecas populares ainda não fornecem uma interface assíncrona não bloqueante, o que as tornava incompatíveis com o NGINX. Podemos gastar muito tempo e recursos desenvolvendo nosso próprio protótipo não bloqueante de alguma biblioteca, mas sempre valerá a pena o esforço? Agora, com pools de threads a bordo, é possível usar essas bibliotecas com relativa facilidade, criando esses módulos sem afetar o desempenho.

Fique atento.

Experimente você mesmo os pools de threads no NGINX Plus – comece seu teste gratuito de 30 dias hoje mesmo ou entre em contato conosco para discutir seus casos de uso .


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."