Citymobil - um manual para melhorar a disponibilidade em meio ao crescimento dos negócios para startups. Parte 4



Este é o próximo artigo da série que descreve como estamos aumentando nossa disponibilidade de serviços no Citymobil (você pode ler as partes anteriores aqui: parte 1 , parte 2 , parte 3 ). Em outras partes, falarei sobre os acidentes e interrupções em detalhes.

1. Liberação incorreta: sobrecarga de banco de dados


Deixe-me começar com um exemplo específico desse tipo de interrupção. Implementamos uma otimização: adicionado USE INDEX em uma consulta SQL; durante os testes e na produção, acelerou as consultas curtas, mas as longas - diminuíram a velocidade. A desaceleração das consultas longas só foi notada na produção. Como resultado, muitas consultas paralelas longas fizeram o banco de dados ficar inativo por uma hora. Estudamos minuciosamente o funcionamento do USE INDEX; nós o descrevemos no arquivo do Do and Dont e alertamos os engenheiros contra o uso incorreto. Também analisamos a consulta e percebemos que ela recupera principalmente dados históricos e, portanto, pode ser executada em uma réplica separada para solicitações históricas. Mesmo que essa réplica ocorra devido a uma sobrecarga, os negócios continuarão funcionando.

Continuamos tropeçando nos mesmos problemas posteriormente e, em algum momento, decidimos tratar desse assunto. Estudamos o código e movemos todas as consultas que pudemos sem comprometer nosso serviço para as réplicas. As próprias réplicas foram divididas dependendo do nível de criticidade, para que nenhuma delas falhasse e parasse o serviço. Como resultado, criamos uma arquitetura com os seguintes bancos de dados:

  • banco de dados mestre - para operações de gravação e consultas que são super sensíveis à atualização de dados;
  • réplicas de produção - para consultas curtas que são menos sensíveis à atualização de dados;
  • réplicas para coeficientes de aumento de preço. Essas réplicas podem ter 30 a 60 segundos de atraso; isso não é crucial, pois os coeficientes não mudam com tanta frequência e, se essa réplica cair, o serviço não parará de funcionar; os preços simplesmente não correspondem totalmente ao equilíbrio de oferta e demanda;
  • réplica para configurações operacionais e da central de atendimento. Se travar, a empresa continuará funcionando, mas sem o suporte do cliente e do driver, e não poderemos alterar temporariamente algumas configurações;
  • muitas réplicas para análises e painéis ad hoc;
  • Banco de dados MPP (processamento massivamente paralelo) para algumas análises massivas com fatias completas nos dados históricos.

Essa arquitetura nos proporcionou um amplo espaço para crescimento e reduziu várias falhas devido a consultas SQL não ideais. Mas ainda está longe de ser perfeito. Planejamos implementar sharding, para que possamos dimensionar atualizações e exclusões e super sensíveis às consultas de atualização de dados. A margem de segurança do MySQL não é infinita. Em breve, precisaremos de uma artilharia pesada na forma de um banco de dados na memória (por exemplo, Tarantool). Definitivamente vou falar sobre isso nos próximos artigos.

Enquanto lidávamos com códigos e consultas não ótimos, entendemos o seguinte: qualquer não idealidade deve ser eliminada antes de ser lançada e não depois. Isso diminui o risco de interrupções e esforços das equipes de engenharia para otimização. Se o código já foi implantado com algumas novas versões, é muito mais difícil otimizá-lo. Como resultado, introduzimos uma revisão obrigatória de código para otimização. É realizado por nossos engenheiros mais experientes, nossa força de elite.

Também começamos a coletar os melhores métodos de otimização de código adequados para a nossa realidade nas tarefas de fazer e não fazer. Eles estão listados abaixo. Por favor, não tome essas práticas como verdade inegável e não tente replicá-las cegamente. Todo método faz sentido apenas para alguma situação específica e negócios específicos. Este é apenas um exemplo para esclarecer os detalhes:

  • Se uma consulta SQL não depender do usuário (por exemplo, mapa de demanda do driver com tarifas máximas e coeficientes de pico - esse mapa é o mesmo para qualquer usuário do aplicativo do driver), essa consulta deverá ser executada em um script cron com alguma frequência específica (uma vez por minuto é suficiente nesse caso). O resultado deve ser gravado em um cache (Memcached ou Redis) que deve ser usado no código de produção.
  • Se uma consulta SQL opera com os dados cujo atraso não é crucial para os negócios, seu resultado deve ser colocado em um cache com algum TTL (30 segundos, por exemplo) e depois ser lido no cache pelo código de produção.
  • Se em uma implementação específica do método do servidor (em PHP ou em outra linguagem do servidor) você decidir fazer uma consulta SQL, verifique se os dados necessários não chegaram com outra consulta SQL ou estão prestes a chegar seu código abaixo.
  • O mesmo que acima, vai para solicitações para um cache. Um cache é rápido, mas ainda é um banco de dados. Portanto, também pode ser sobrecarregado. Um erro comum é que você acha que um cache é uma espécie de variável normal na memória e o usa como variável. Mas o acesso a ele envolve uma ida e volta da rede e gera uma carga de trabalho para o Redis ou o Memcached. Portanto, se os dados já chegaram do cache, não retire apenas do cache o que já foi obtido.
  • Se durante um processamento de solicitação da web (novamente em PHP ou qualquer outro idioma) você precisar chamar uma função, verifique se não haverá consultas SQL adicionais ou acesso ao cache. Se uma chamada de função for inevitável, verifique se ela não pode ser modificada ou se a lógica não foi quebrada para evitar consultas desnecessárias ao banco de dados / cache.
  • Se for necessário executar uma consulta SQL, você deve ter certeza absoluta de que os campos necessários não podem ser adicionados a consultas já existentes no seu código acima ou abaixo.

2. Mau funcionamento manual


Exemplos desses acidentes:

  • ALTER inválido que sobrecarregou o banco de dados ou causou atraso na réplica;
  • DROP ruim (por exemplo, encontramos um bug no MySQL que bloqueava o banco de dados ao mesmo tempo que soltava uma tabela);
  • uma consulta pesada em um mestre, feita manualmente por engano;
  • um servidor da web estava sendo configurado, apesar de estar sob a carga de trabalho real, enquanto pensávamos que estava fora de operação.

Para minimizar interrupções devido a esses motivos, precisamos investigar a natureza de um acidente toda vez que ela ocorrer. Ainda não descobrimos uma regra geral. Novamente, vejamos alguns exemplos específicos. Os coeficientes de surto (tarifa de táxi no momento e local de alta demanda são multiplicados por eles) pararam de funcionar em algum momento. O motivo foi que havia um script python trabalhando em um servidor de réplica de banco de dados em que os dados para o cálculo dos coeficientes foram retirados e o script usou toda a memória e a réplica caiu. O script já estava em execução há um tempo; estava operando diretamente na réplica por uma questão de conveniência. O problema foi resolvido pela reinicialização do script. As seguintes conclusões foram tiradas: não execute scripts estrangeiros em um servidor de banco de dados (ele foi escrito em fazer e não fazer; caso contrário, seria um tiro vazio!), Monitore o uso de memória em um servidor de banco de dados e alerta via SMS se esse servidor estiver prestes a ficar sem memória.

É essencial sempre tirar conclusões e não se sentir confortável no tipo de situação "vi o problema, consertei, esqueci". Um serviço de qualidade só pode ser oferecido se alguém sair com uma conclusão. Além disso, os alertas por SMS são críticos: aumentam o nível de qualidade do serviço, não diminuem e permitem aumentar a confiabilidade. Como um alpinista que chega a uma posição estável e depois se coloca em outra posição estável, mas apenas dessa vez.

O monitoramento e os alertas não são visíveis, mas agem como ganchos de ferro cortando pedras desconhecidas, impedindo-nos de ficar abaixo do nosso contrato de nível de serviço que estamos aumentando continuamente.

3. ovo de pascoa


O que chamamos de "um ovo de Páscoa" - é uma mina de ação retardada que ainda não tropeçamos, apesar de existir há algum tempo. Fora deste artigo, esse termo é usado para recursos não documentados criados de propósito. No nosso caso, não é um recurso, mas um bug que age como uma bomba-relógio e aparece como efeito colateral de alguma atividade bem-intencionada.

Por exemplo:

  • transbordamento de auto_increment de 32 bits;
  • não idealidade do código / configuração, acionada por alta carga de trabalho;
  • réplica atrasada por uma consulta não ideal provocada por um novo padrão de uso ou por uma carga de trabalho mais pesada;
  • réplica atrasada por uma operação UPDATE não ideal no mestre que foi causada por um novo padrão de carga de trabalho e atrasou a replicação.

Outro tipo popular de ovo de Páscoa é o código não ideal; para ser mais específico - consulta SQL não ideal. A tabela costumava ser menor e a carga de trabalho era mais leve - a consulta funcionou bem. Com o crescimento linear da tabela de horários e o aumento linear da carga de tempo, o consumo de recursos por um sistema de gerenciamento de banco de dados estava crescendo quadraticamente. Geralmente, isso leva a um efeito negativo drástico: é como se tudo estivesse bem e de repente - oops!

Cenários mais raros - combinação de insetos e ovos de Páscoa. Um lançamento com um bug levou ao aumento de uma tabela de banco de dados e aumentou várias linhas de um tipo específico, enquanto um ovo de Páscoa já existente causou sobrecarga no banco de dados devido às consultas mais lentas dessa tabela expandida.

No entanto, costumávamos ter alguns ovos de Páscoa não relacionados à carga de trabalho. Por exemplo, um campo de auto_increment 32 bits no MYSQL. Após pouco mais de 2 bilhões de linhas, as inserções falham. Portanto, no mundo moderno, devemos usar apenas os campos de auto_increment 64 bits. Aprendemos bem essa lição.

Como lidar com ovos de Páscoa? A resposta parece direta: a) procure os ovos velhos, b) não permita que os novos apareçam. Estamos tentando fazer as duas coisas. Uma busca pelos ovos antigos acompanha a nossa otimização contínua do código. Nomeamos dois dos engenheiros mais experientes para executar a otimização quase em tempo integral. Eles encontram consultas no slow.log que usam mais os recursos dos bancos de dados; eles otimizam essas consultas e o código ao seu redor. Reduzimos a possibilidade de surgimento de novos óvulos através do teste de todas as confirmações quanto à otimização realizada pelos engenheiros sensei mencionados acima. Sua tarefa é apontar os erros que afetam o desempenho, sugerir a maneira de melhorar as coisas e passar esse conhecimento para outros engenheiros.

Em algum momento, logo após encontrar outro ovo de Páscoa, percebemos que era bom procurar consultas lentas, mas também deveríamos ter procurado as consultas que parecem lentas, mas funcionam rapidamente. Estes são os próximos candidatos a travar tudo em caso de outro crescimento explosivo da mesa. O exemplo estúpido, mas óbvio, aqui é uma consulta que varre por completo uma tabela de 10 linhas sem usar índices. Por enquanto, ele trabalhará rápido. No entanto, quando a tabela for grande o suficiente, a consulta reduzirá o banco de dados. Esse é o próprio ovo da páscoa.

4. Causas externas


Essas são as causas que parecemos incapazes de controlar muito bem. Dito de outra forma, essas são as causas que só podem ser mitigadas, mas não eliminadas. Por exemplo:

  • Regulando nossos pedidos por um provedor de serviços de mapas. Ele pode ser mitigado por meio do controle de uso do serviço, adesão a um nível específico de carga de trabalho, planejamento antecipado do aumento da carga de trabalho e compra de expansão de serviço. No entanto, não podemos ficar sem mapas.
  • Falha na rede em um data center. Isso pode ser mitigado colocando a cópia do serviço em um data center de backup. No entanto, não podemos prescindir de um data center físico ou na nuvem.
  • Serviço de pagamento inativo. Pode ser mitigado pelo backup dos serviços de pagamento. No entanto, não podemos ficar sem pagamentos.
  • Bloqueio de tráfego incorreto por um serviço de proteção DDoS. Ele pode ser mitigado desativando o serviço de proteção DDoS por padrão, ativando-o apenas no caso de um ataque DDoS. No entanto, não podemos prescindir da proteção contra DDoS.

Como até a mitigação de uma causa externa é um esforço longo e caro, começamos a coletar estatísticas para acidentes causados ​​por razões externas e aguardamos o acúmulo crítico de massa. Não temos uma receita para definir uma massa crítica. É tudo sobre mera intuição. Por exemplo, se estivéssemos completamente inativos cinco vezes devido a, digamos, problemas de serviço de proteção contra DDoS, a cada tempo de inatividade subsequente a necessidade de alternativa se tornaria cada vez mais aguda.

Por outro lado, se podemos de alguma forma fazer tudo funcionar com um serviço externo indisponível, nós definitivamente o fazemos. A análise post mortem de cada interrupção nos ajuda aqui. Sempre deve haver uma conclusão. O que significa que, gostemos ou não, sempre apresentamos uma solução alternativa.



Na parte final, vou falar sobre mais um tipo de interrupções e as conclusões que tiramos sobre elas, como modificamos o processo de desenvolvimento, que automação introduzimos. Fique atento!

Source: https://habr.com/ru/post/pt450678/


All Articles