Olá Habr! Você também está assistindo curiosamente o "épico do oeste selvagem americano na distribuição de terrenos - vá primeiro e coloque uma bandeira na estaca" ou talvez até participe. Mais precisamente em sua versão moderna - seja o primeiro a solicitar serviços públicos para receber dinheiro para crianças ou obter um passe para sair de casa. Olhando para tudo isso, gostaria de compartilhar a experiência de nossa equipe em testar e participar da preparação de um portal regional de serviços para a prestação do serviço "First Class Record". Também é muito parecido com o efeito habra e, acho, foi próximo ao que aconteceu alguns dias atrás com o portal federal gosuslugi.ru, mas em escala regional.Superamos esse desafio em Khabarovsk em janeiro deste ano e recentemente participamos da preparação de um serviço semelhante para a emissão de licenças de caça em outra região. Abaixo está uma pequena experiência que lhe dará a oportunidade de analisar a questão da preparação do trabalho dos portais regionais de serviços públicos para períodos de pico de outra perspectiva.E para iniciantes - uma foto de um ônibus preto, que estava de serviço na escola por três dias, 24 horas por dia, no qual o autor dessas linhas confirmou sua vez entre os pais há três anos. Pelo menos nossos pais se uniram. Assistindo no inverno em Khabarovsk a -30 graus ainda é um prazer.
No processo de preparação para o pico de gravação na primeira série, várias equipes trabalharam, porque de uma maneira ou de outra: o operador do centro de dados, o operador e o desenvolvedor do sistema de informações do portal regional, o operador e o desenvolvedor do sistema integrado de informações da educação, o suporte técnico aos usuários do portal regional. Na iondv, realizamos a última tarefa, monitorando independentemente a saúde do portal e dando suporte aos usuários.Nosso papel na preparação é organizar testes e recomendações sobre configurações de armazenamento em cache no nginx. Bem, também preparamos instruções para usuários com o "comportamento" recomendado.Para quem não conhece o problema da escrita na 1ª série, . . , , - ( , — ), , - , , . , , , – , . .
1- , . 0:00 , 10:00 26 . , – . 10:00 , — , - .
.
. 2017 . . , , . .
Um serviço para um recurso exigido como uma tarefa técnica
O problema em serviços como "escrever para a 1ª série" no indicador integral da carga (ou probabilidade de consultas) tendendo à "função delta" (função δ, função Dirac) é claramente visível nos gráficos na forma de picos. Neste momento, há um aumento múltiplo de chamadas em um curto período de tempo.
Nossa experiência diz que a principal tarefa do treinamento não é aumentar recursos. A tarefa é minimizar o número potencial de solicitações por segundo, esticá-las por um período e preparar o sistema para a carga restante. Nesse caso, é necessário encontrar e acelerar gargalos - isso produzirá o maior efeito de acordo com os princípios da teoria dos sistemas limitados (princípios de Goldratt). E, caso contrário, é o gargalo que irá falhar. Todo o sistema deveria funcionar com ele: o princípio da "corda do tambor".É fisicamente impossível derramar o volume inteiro em 10 minutos de ampulheta em 1 minuto - é óbvio que eles entrarão em colapso. Da mesma forma para a prestação de serviços. Não surpreende ninguém - quando é a vez do MFC e dos escândalos de receber serviços, mas surpreende a todos - por que o portal foi desativado.Existem diferentes padrões de comportamento de manipulação de carga da teoria das filas :- Você pode colocar os usuários em espera, ou seja, aumentar a fila;
- você pode simplesmente se recusar a atender aqueles que vieram mais tarde, por exemplo, até que os anteriores sejam processados;
- Você pode tentar aumentar infinitamente a produtividade.
A conveniência está algures no meio. De fato, para um serviço com um recurso limitado fornecido por uma região ou estado, não apenas a velocidade é importante, mas, antes de tudo, a preservação da justiça social - ou seja, igualdade de condições para todos. Ao mesmo tempo - se o usuário não recebeu o que precisa, ele inicia uma nova solicitação. Nesse caso, as solicitações crescem em uma avalanche, formando um modelo de ataque “ efeito de pilha de cachorro ” (efeito de pilha de cachorro, carimbo de cache, tempestade de acertos) - o usuário já cancelou a solicitação e iniciou uma nova, enquanto a anterior ainda está na fila a ser processada.Esse processo reforça o fato de famílias inteiras participarem da submissão - pai e mãe preenchem as solicitações ao mesmo tempo e frequentemente enviam solicitações várias vezes por questões de confiabilidade. Além disso, muitas vezes também em várias guias e vários navegadores. Portanto, o pico de carga esperado geralmente faz sentido multiplicar por 2-3 vezes, a partir do número daqueles que realmente solicitam esses serviços.Retiro da justiça, «», . ? «» , . . — , . « ». . .
Organização da prestação de serviços
Calculamos o número esperado de candidatos com base em uma combinação de dados sobre o número total de solicitações enviadas no ano passado e dados por minuto para outras regiões. Normalmente, o pico de aplicativos cai em 5 a 10 minutos, inclusive porque os portais quase não respondem nos primeiros três a cinco minutos, e os usuários posteriores preenchem o formulário de 1 a 5 minutos (não se surpreenda, muitos são preenchidos pelo telefone, mesmo em condições "nervosas") .Um modelo de cálculo aproximado para as 1000 aplicações condicionais por hora é o seguinte:- pico de 5 a 10 minutos desde o início e 80% dos pedidos serão apresentados sob a regra de Paretto
- Convencionalmente, planejamos 160 aplicativos por minuto ou 3 aplicativos por segundo.
De fato, o primeiro envio ocorreu após um minuto e 45 segundos, e o pico de pedidos passou de 4 minutos.Para reduzir a carga no ESIA e no sistema de gerar sessões de autorização, as instruções sugeriram que os usuários efetuem login com antecedência e prolongem a vida útil da sessão. De fato, 50% foram autorizados em 1 hora e ~ 90% em meia hora. Descobrimos anteriormente que os usuários começaram a fazer login no portal 10 minutos antes do início do serviço - e a autorização começou a funcionar de maneira instável. É difícil dizer o porquê. Talvez a razão seja a seguinte: quando o trabalho técnico é realizado à noite em Moscou, em Khabarovsk, temos apenas o começo do dia útil.Retiro sobre instruções e arranjos organizacionais.
, « » . .. . , - ..
, , , . - . , , .
É impossível remover a "função delta" quando o formulário é recarregado às 00:00. O objetivo deste procedimento é que o serviço seja exibido em um determinado momento. Mas você pode tentar reduzir o número de solicitações de navegador em todas as rotas de usuário esperadas e, assim, deixar a carga no sistema apenas das necessárias - formulário, diretórios dinâmicos e aplicativos de envio.As próprias configurações do nginx são bastante padrão. Aqui é mais importante escolher as limitações que o sistema pode suportar. Pegue-os - ou seja, iniciar solicitações de enfileiramento quando o servidor atingir o limite de seus recursos.Bem, e o mais importante, forçamos o cache (proxy_cache) e aumentamos a vida útil dos dados "expira" no nginx para todos os caminhos estáticos e, sempre que possível, páginas dinâmicas nas quais não há sessões. A propósito, esse é um erro comum ao armazenar em cache - gravando nos dados do cache (às vezes até na estática) em que a sessão de outra pessoa é salva, a saída geralmente é excluir esses cookies dos cabeçalhos se o servidor não puder separar os tipos de dados.No navegador do usuário, parece atualizar páginas de arquivos baixados do disco ou da memória. Mas mesmo quando o usuário os obtém do servidor, eles são retirados do cache nginx. Os próprios diretórios, é claro, são armazenados em cache no próprio sistema.
Isso reduziu o número de solicitações em potencial de 89 solicitações para 14 e o volume de 2,1 MB (para 1000 usuários que atualizaram a página, este é um pico potencial de 4-8 Gbit / s) para 38 Kb (todos lembramos do webpack, mas para plataformas de empresa, isso não é sempre fácil de fazer). De acordo com os resultados da passagem, ainda era necessário armazenar em cache não apenas no sistema, mas também no nginx, alguns dos diretórios do formulário e classificadores dinâmicos não utilizados no momento de pico e forçar a vida útil deles. E com o aumento da carga, geralmente faz sentido colocar a página principal totalmente estática com o roteamento de usuários para o serviço desejado ou criar um recurso separado para o serviço.Para reduzir a carga no envio, os rascunhos e o preenchimento automático de dados para a criança foram desativados. Todos os usuários têm velocidades diferentes de entrada de dados, o que elimina a aparência de um formulário completamente pronto para envio e evita a função delta para o envio de aplicativos - todos os 1000 em um minuto. Ao mesmo tempo, a justiça social é mantida, embora, é claro, surjam reclamações.Não descreverei a otimização do próprio sistema - durante o teste de carga, foram identificados gargalos - principalmente nas consultas DBMS e os próprios índices e consultas foram otimizados.Provavelmente a otimização mais importante é simplificar o formulário. O que afeta mais a velocidade quando implementado em um formulário?- — , , . — 5-10 ( iPhone ) 5- 375 / (1 10 ,
application/x-www-form-urlencoded
– 20 ), 100 625 /. 100/ — . , « ». — ? , . , ? - guias sofisticados. A carga geralmente é aumentada usando o diretório de endereço FIAS ou CLADR. Os problemas aqui são devidos ao tamanho - o FIAS leva até 40 GB no banco de dados e leva tempo para procurá-lo. Décimos de segundo, mas multiplicados por 1000 solicitações simultâneas, carregam qualquer sistema. Sem preparação especial, possivelmente na forma de um serviço da Web separado e em um recurso separado, é difícil suportar a carga - portanto, eles costumam usar um campo de texto sem formatação para o endereço.
Bem, vamos aos testes.Teste de carga em preparação
O teste foi realizado através do operador de marionetes - emulando as ações do usuário no navegador Crominium. Yanedeks.tank e JMeter eliminam a proteção contra ataques, porque eles geram muitos dos mesmos tipos de solicitações. Além disso, esses testes coincidem fracamente com o perfil de consultas reais ao alterar o comportamento do sistema sob carga. Além disso, os servidores armazenam em cache solicitações e é difícil reproduzir parte dos processos neles (por exemplo, autorização). A propósito, em um dos seminários do devDV , temos uma apresentação sobre o uso de marionetistas para testes, incluindo carregar, link para o vídeo .Para começar, compilamos um perfil de comportamento do usuário e dividimos o procedimento em etapas principais:- autorização em massa na ESIA
- atualização única do formulário de serviço,
- alimentação em massa
Para cada uma das etapas, fizemos um teste separado.No ano passado, houve dificuldades na fase de autorização na ESIA, mas testá-lo em larga escala é difícil. O sistema é externo, a proteção contra ataques e proibições de autorização é acionada. No entanto, é possível formular um perfil de teste para testar com precisão os gargalos do sistema em teste - geralmente esse é o número de sessões autorizadas simultaneamente e os valores de autorização planejados por minuto, que podem ser regulados por recomendações.No teste, o wrapper é importante para organizar vários threads, usamos o 'puppeteer-cluster'. Mas geralmente é mais complicado lidar com exceções e alterar o comportamento do portal sob carga - os elementos de layout geralmente são revelados e aparecem duas vezes. Ou os elementos não aparecem se alguns dados não foram carregados conforme o esperado. Esses são todos os erros que os usuários verão e recarregam a página - o que significa que eles criarão uma carga adicional. Existem duas maneiras: implementar o tratamento de exceções no teste. Ou modifique o portal.O teste em si é simples. Abaixo está um fragmento, clicando no botão "Login" no portal de serviços para inserir dados na ESIA.await page.waitForSelector(AUTH_AVAIL,{timeout:OPT_ELEM_WAIT_TIME});
const needAuth = await page.$(ELEM_AUTH_IN);
if (!needAuth) throw (new Error(` `));
await page.waitForSelector(AUTH_BUT, OPT_ELEMENT_VISIBLE);
await page.click(AUTH_BUT);
await waitNewUrl(page, 'https://esia.gosuslugi.ru/idp/rlogin?cc=bp', OPT_PAGE_WAIT_TIME);
await page.waitForSelector('#mobileOrEmail', OPT_ELEMENT_VISIBLE);
let text = await elemGetText(page, '#authnFrm > div.login-slils-box > div > div.detected > div.left > div.this-user');
if (text)
text = text.replace(/ -\(\)/g, '');
if (text && text.indexOf(user) === -1) {
await page.click('div.click-to-another > a');
await page.waitForSelector('#authnFrm > div.login-slils-box > div >' +
' div.detected > div.left > div.this-user', OPT_ELEMENT_INVISIBLE);
}
await page.waitForSelector('#password', OPT_ELEMENT_VISIBLE);
await page.type('#mobileOrEmail', user);
await page.type('#password', pwd);
await page.click('#loginByPwdButton');
Verificação da atualização do formulário de inscrição com usuários pendentes "abrindo o registro". O teste de reinicialização é essencialmente uma etapa, mas é importante verificar os tipos de erros retornados - a rede é um problema, um erro nginx, um erro do servidor e se o formulário atende aos critérios. E a dificuldade é gerar o volume máximo de solicitações no menor tempo possível e não se enquadrar nas restrições de proteção (no entanto, durante os testes, pode ser alterado, por outro lado, é também uma verificação das configurações de infraestrutura de rede e servidor e WAF).Tais testes em marionetistas exigem muitos recursos para funcionar. De fato, você precisa de pelo menos 2 núcleos contra o primeiro núcleo do subsistema front-end e um canal muito amplo. Mas ao alugá-los na nuvem - isso é bastante acessível. Usamos o Yandex.cloud.No teste, a autorização é implementada primeiro na ESIA para cada fluxo separadamente. Depois disso, um navegador separado é iniciado para cada encadeamento e, dentro da estrutura de uma instância, um determinado número de atualizações é realizado. Depois disso, a instância reinicia. A verificação em si pode incluir um caminho típico, por exemplo, a página principal, a forma do serviço. Porém, mais frequentemente, basta atualizar completamente o serviço e verificar o diretório necessário para que o serviço possa ser enviado - tudo como nas instruções para os usuários.
Um fragmento do teste para abrir o principal e atualizar a página.try {
await page.setViewport(PUP_OPT);
await page.goto(BASE_URL);
await page.setCookie(...cookies[worker.id]);
await page.goto(`${BASE_URL}/nd/lk/form/dnv.htm`);
rdyRefresh++;
} catch (err) {
console.error(`# ${data}: ${err.message}`);
getErr++;
await page.screenshot({path: filename});
}
for (let i = 0; i < AMOUNT_REFRESH - 1; i++) {
const filenameIter = path.join(BASE_DIR, PIC_DIR, `${data}-${i}.png`);
try {
await page.reload({waitUntil: ["networkidle0", "domcontentloaded"]});
rdyRefresh++;
} catch (err) {
if (!err.message.includes('Navigation failed because browser')) {
console.error(`# ${data}-${i}: ${err.message}`);
getErr++;
await page.screenshot({path: filenameIter});
}
}
}
Para a carga enviando aplicativos, todo o ciclo de verificação foi implementado - com uma recarga do formulário e verificação da entrada de todos os dados.Fragmento.for (let i = 0; i < AMOUNT_RESEND; i++) {
const filename = path.join(BASE_DIR, PIC_DIR, `${data}-${i}.png`);
try {
await page.goto('https://uslugi27.ru/nd/lk/form/dnv.htm');
} catch (err) {
console.error(`# 1 ${data}-${i}: ${err.message}`);
await page.screenshot({path: filename});
getErr++;
continue;
}
try {
const FORM_PREF = '#createForm > div:nth-child(4) > ';
await clickDelayed(page,`${FORM_PREF}fieldset.petgroup.ungroupped-attrs > div > div:nth-child(4) > div.col-md-9.attr-data`);
await page.type(`${FORM_PREF}fieldset:nth-child(2) > div > div:nth-child(1) > div.col-md-9.attr-data > input`, '');
} catch (err) {
console.error(`# ${data}-${i}: ${err.message}`);
await page.screenshot({path: filename});
continue;
}
try {
await page.click('#createForm > div.col_100.controls > button.btn.btn-primary.pull-right.next');
await clickDelayed(page,`#createForm > div:nth-child(5) > fieldset > div > div:nth-child(1) > div > div`);
await page.click('#createForm > div:nth-child(5) > fieldset > div > div:nth-child(2) > div > div');
await page.click('#createForm > div.col_100.controls > button.btn.btn-success.pull-right.submit');
} catch (err) {
console.error(`# ${data}-${i}: ${err.message}`);
await page.screenshot({path: filename});
sendErr++;
continue;
}
A propósito, a aprovação no teste pode ser acelerada se você digitar todos os dados que não são do apresentador de marionetes pela construção aguardar page.type, mas transferir essa lógica para o próprio navegador. Mas então a complexidade da captura de erros aumenta. Igual adocument.querySelector('#createForm > div:nth-child(4) > fieldset.petgroup.ungroupped-attrs > div > div:nth-child(4) > div.col-md-9.attr-data').click();
document.querySelector('#createForm > div:nth-child(4) > fieldset:nth-child(2) > div > div:nth-child(1) > div.col-md-9.attr-data > input').value = '';
Durante os testes, fornecemos vários milhares de autorizações ESIA e cerca de 16 mil aplicativos enviados. Como foi a restauração de um sistema produtivo de informações educacionais após tantas declarações - nem pergunte. Esta é uma história completamente diferente.O principal resultado visível desse processo foi que a mídia local estava entediada agora nos dias de matrícula na primeira série. O serviço saiu da área de mídia.Paralelamente, criamos um painel para monitorar o desempenho do formulário com base no Grafana: número de aplicativos, número de chamadas, métricas Yandex etc. Mas deixaremos esse tópico para a próxima vez.Bem, gostaria de felicitar todos os que estão conectados com o tópico de melhorar a qualidade da prestação de serviços estaduais e municipais em formato eletrônico. Esse trabalho preparatório interminável não foi em vão - afinal, em abril e maio, o número de solicitações apresentadas aumentou significativamente.