AWS RDS MySQL desacelerando com o tempo

Aug 24 2020

Eu li muitos posts sobre este tópico, mas nenhum deles fala sobre o banco de dados AWS RDS MySQL. Há três dias, estou executando um script Python em uma instância AWS EC2 que escreve linhas em meu banco de dados AWS RDS MySQL. Tenho que escrever 35 milhões de linhas, então sei que isso vai levar algum tempo. Periodicamente, verifico o desempenho do banco de dados e, três dias depois (hoje), percebo que o banco de dados está ficando lento. Quando começou, as primeiras 100.000 linhas foram escritas em apenas 7 minutos (este é um exemplo das linhas com as quais estou trabalhando)

0000002178-14-000056    AccountsPayableCurrent  us-gaap/2014        20131231    0   USD 266099000.0000

Após três dias, 5.385.662 linhas foram gravadas no banco de dados, mas agora leva quase 3 horas para gravar 100.000 linhas. O que está acontecendo?

A instância EC2 que estou executando é t2.small. Aqui você pode verificar as especificações, se necessário: EC2 SPECS . O banco de dados RDS que estou executando é o db.t2.small. Verifique as especificações aqui: RDS SPECS

Anexarei aqui alguns gráficos sobre o desempenho do banco de dados e da instância EC2: Db CPU / Db Memória / Db Write IOPS / Db Write Throughput / Rede EC2 em (bytes) / Saída de Rede EC2 (bytes)

Seria ótimo se você pudesse me ajudar. Muito obrigado.

EDIT 1: Como estou inserindo linhas? Como eu disse antes, tenho um script Python em execução em uma instância EC2, esse script lê arquivos de texto, faz alguns cálculos com esses valores e, em seguida, grava cada "nova" linha no banco de dados. Aqui está um pequeno trecho do meu código. Como leio os arquivos de texto?

for i in path_list:
  notify("Uploading: " + i)
  num_path = "path/" + i + "/file.txt"
  sub_path = "path/" + i + "/file.txt"

  try:
    sub_dict = {}
    with open(sub_path) as sub_file:
      for line in sub_file:
        line = line.strip().split("\t")
        sub_dict[line[0]] = line[1] # Save cik for every accession number
        sub_dict[line[1] + "-report"] = line[25] # Save report type for every CIK
        sub_dict[line[1] + "-frecuency"] = line[28] # Save frecuency for every CIK

    with open(num_path) as num_file:
      for line in num_file:
        num_row = line.strip().split("\t")

        # Reminder: sometimes in the very old reports, cik and accession number does not match. For this reason I have to write 
        # the following statement. To save the real cik.

        try: 
          cik = sub_dict[num_row[0]]
        except:
          cik = num_row[0][0:10]

        try: # If there is no value, pass
          value = num_row[7]
          values_dict = {
                  'cik': cik, 
                  'accession': num_row[0][10::].replace("-", ""),  
                  'tag': num_row[1], 
                  'value': value, 
                  'valueid': num_row[6], 
                  'date': num_row[4]
                  }

          sql = ("INSERT INTO table name (id, tag, value_num, value_id, endtime, cik, report, period) "
              "VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}')".format(
                  values_dict['cik'] + values_dict['accession'] + values_dict['date'] + values_dict['value'].split(".")[0] + "-" + values_dict['tag'], 
                  values_dict['tag'], 
                  float(values_dict['value']), 
                  values_dict['valueid'], 
                  values_dict['date'], 
                  int(values_dict['cik']), 
                  sub_dict[values_dict['cik'] + "-report"], 
                  sub_dict[values_dict['cik'] + "-frecuency"]
                  ))

          cursor.execute(sql)
          connection.commit()

Eu sei que não há except:como analisar as tryafirmações, mas isso é apenas uma parte do roteiro. Acho que a parte importante é como insiro cada linha. Caso não precise fazer cálculos com os valores, utilizarei Load Data Infilepara escrever os arquivos texto no banco de dados. Acabei de perceber que talvez não seja uma boa ideia commitsempre que insiro uma linha. Vou tentar confirmar após 10.000 linhas ou mais.

Respostas

11 MLu Aug 24 2020 at 06:03

As instâncias T2 e T3 (incluindo instâncias db.t2 db.t3) usam o sistema de crédito da CPU . Quando a instância está ociosa, ela acumula créditos de CPU que pode usar para executar mais rapidamente por curtos períodos de tempo - desempenho de burst . Depois de esgotar os créditos, ele diminui para um desempenho de linha de base .

Uma opção é habilitar a configuração T2 / T3 Unlimited em sua configuração RDS, o que permitirá que a instância seja executada em velocidade total pelo tempo necessário, mas você pagará pelos créditos extras necessários.

A outra opção é alterar o tipo de instância para db.m5 ou algum outro tipo não T2 / T3 que suporte desempenho consistente.

Aqui está uma explicação mais detalhada sobre os créditos da CPU e como eles são acumulados e gastos: Como esclarecer as condições de trabalho de t2 e t3?

Espero que ajude :)

9 RickJames Aug 24 2020 at 07:09
  • Uma linha INSERTsé 10 vezes mais lenta que 100 linhas INSERTsou LOAD DATA.

  • UUIDs são lentos, especialmente quando a tabela fica grande.

  • UNIQUEos índices precisam ser verificados antes de terminar um iNSERT.

  • Não exclusivo INDEXespode ser feito em segundo plano, mas ainda assim exige um pouco de carga.

Forneça SHOW CREATE TABLEe o método usado para INSERTing. Pode haver mais dicas.

7 tater Aug 23 2020 at 23:07

Cada vez que você confirma um índice de transação precisa ser atualizado. A complexidade da atualização de um índice está relacionada ao número de linhas na tabela, portanto, à medida que o número de linhas aumenta, a atualização do índice se torna progressivamente mais lenta.

Supondo que você esteja usando tabelas InnoDB, você pode fazer o seguinte:

SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
ALTER TABLE table_name DISABLE KEYS;

Em seguida, faça as inserções, mas agrupe-as de forma que uma instrução insira (por exemplo) várias dezenas de linhas. Gosto INSERT INTO table_name VALUES ((<row1 data>), (<row2 data>), ...). Quando as inserções terminarem,

ALTER TABLE table_name ENABLE KEYS;
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;
COMMIT;

Você pode ajustar isso para sua própria situação, por exemplo, se o número de linhas for grande, então talvez você queira inserir meio milhão e depois confirmar. Isso pressupõe que seu banco de dados não está 'ativo' (isto é, usuários ativamente lendo / gravando nele) enquanto você está fazendo as inserções, porque você está desabilitando as verificações nas quais poderia confiar ao inserir os dados.