Labirinto de Desempenho NoSQL da AWS Usando Python

Em muitas empresas financeiras, o processamento de transações online (OLTP) geralmente depende de dados estáticos ou pouco atualizados, também chamados de dados de referência. As fontes de dados de referência nem sempre exigem capacidades de transação ACID, mas precisam de suporte para consultas de leitura rápidas, geralmente baseadas em padrões simples de acesso a dados, e arquitetura orientada a eventos para garantir que os sistemas alvo permaneçam atualizados. Bancos de dados NoSQL surgem como candidatos ideais para atender a essas necessidades, e plataformas em nuvem como o AWS oferecem ecossistemas de dados gerenciados e altamente resilientes.

Neste artigo, não vou determinar qual banco de dados NoSQL do AWS é melhor: o conceito de um banco de dados melhor existe apenas dentro de um contexto específico e intencional. Vou compartilhar um laboratório de codificação para medir o desempenho dos bancos de dados NoSQL gerenciados pelo AWS, como DynamoDB, Cassandra, Redis e MongoDB.

Teste de Desempenho

I will start by defining the performance test case, which will concurrently insert a JSON payload 200 times and then read it 200 times.

Payload JSON

A classe base/parent em base_db.py implementa a lógica do caso de teste de executar 10 threads concorrentes para criar e ler 200 registros.

Python

 

#imports
.....
class BaseDB:

    def __init__(self, file_name='instrument.json', threads=10, records=20):

      ...................................
      
    def execute(self):

        create_threads = []
        for i in range(self.num_threads):
            thread = threading.Thread(
                target=self.create_records, args=(i,))
            create_threads.append(thread)
            thread.start()

        for thread in create_threads:
            thread.join()

        read_threads = []
        for i in range(self.num_threads):
            thread = threading.Thread(target=self.read_records, args=(i,))
            read_threads.append(thread)
            thread.start()

        for thread in read_threads:
            thread.join()

        self.print_stats()

Cada thread executa a rotina de escrita/leitura no create_records e read_records, respectivamente. Observe que essas funções não incluem nenhuma lógica específica de banco de dados, mas sim, medem o desempenho de cada execução de leitura e escrita.

Python

 

def create_records(self, thread_id):

  for i in range(1, self.num_records + 1):
    key = int(thread_id * 100 + i)
    start_time = time.time()
    self.create_record(key)
    end_time = time.time()
    execution_time = end_time - start_time
    self.performance_data[key] = {'Create Time': execution_time}


def read_records(self, thread_id):

  for key in self.performance_data.keys():
    start_time = time.time()
    self.read_record(key)
    end_time = time.time()
    execution_time = end_time - start_time
    self.performance_data[key]['Read Time'] = execution_time

Uma vez que o caso de teste é executado, a função print_stats imprime as métricas de execução, como a média de leitura/escrita e os valores de desvio padrão (stdev), que indicam o desempenho e a consistência do banco de dados (um stdev menor implica um desempenho de execução mais consistente).

Python

 

def print_stats(self):

  if len(self.performance_data) > 0:
    # Criar um DataFrame do Pandas a partir dos dados de desempenho
    df = pd.DataFrame.from_dict(self.performance_data, orient='index')

    if not df.empty:
      df.sort_index(inplace=True)
      # Calcular a média e o desvio padrão para cada coluna
      create_mean = statistics.mean(df['Create Time'])
      read_mean = statistics.mean(df['Read Time'])
      create_stdev = statistics.stdev(df['Create Time'])
      read_stdev = statistics.stdev(df['Read Time'])

      print("Performance Data:")
      print(df)
      print(f"Create Time mean: {create_mean}, stdev: {create_stdev}")
      print(f"Read Time mean: {read_mean}, stdev: {read_stdev}")

Código NoSQL

Ao contrário dos bancos de dados relacionais que suportam SQL padrão, cada banco de dados NoSQL possui seu próprio SDK. As classes de casos de teste filho para cada banco de dados NoSQL precisam apenas implementar um construtor e as funções create_record/read_record que contêm o SDK de banco de dados proprietário para instanciar uma conexão de banco de dados e para criar/ler registros em algumas linhas de código.

Caso de Teste DynamoDB

Python

 

import boto3
from base_db import BaseDB

class DynamoDB (BaseDB):

    def __init__(self, file_name='instrument.json', threads=10, records=20):

        super().__init__(file_name, threads, records)

        dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
        table_name = 'Instruments'
        self.table = dynamodb.Table(table_name)

    def create_record(self, key):

        item = {
            'key': key,
            'data': self.json_data
        }
        self.table.put_item(Item=item)

    def read_record(self, key):

        self.table.get_item(Key={'key': key})


if __name__ == "__main__":

    DynamoDB().execute()

Configuração AWS

 Para executar esses casos de teste de desempenho em uma conta AWS, você deve seguir estes passos:

  1. Criar um papel IAM de EC2 com privilégios para acessar os serviços de dados AWS necessários.
  2. Iniciar uma instância EC2 e atribuir o novo papel IAM criado.
  3. Criar cada instância de banco de dados NoSQL.

 Função IAM

Tabela DynamoDB

Espaço/Tabela Cassandra

Por favor, note que o host e as credenciais do banco de dados foram codificados e removidos nos módulos mongo_db.py e redis_db.py e precisarão ser atualizados com as configurações de conexão do banco de dados correspondentes à sua conta AWS. Para conectar-se ao DynamoDB e Cassandra, optei por usar as credenciais da sessão do Boto3 temporariamente atribuídas à db_performnace_iam_role Função IAM. Este código será executado em qualquer conta AWS na região Leste 1 sem qualquer modificação.

Python

 

class CassandraDB(BaseDB):

    def __init__(self, file_name='instrument.json', threads=10, records=20):

        super().__init__(file_name=file_name, threads=threads, records=records)

        self.json_data = json.dumps(
            self.json_data, cls=DecimalEncoder).encode()

        # Configuração de Espaços Cassandra
        contact_points = ['cassandra.us-east-1.amazonaws.com']
        keyspace_name = 'db_performance'

        ssl_context = SSLContext(PROTOCOL_TLSv1_2)
        ssl_context.load_verify_locations('sf-class2-root.crt')
        ssl_context.verify_mode = CERT_REQUIRED

        boto_session = boto3.Session(region_name="us-east-1")
        auth_provider = SigV4AuthProvider(session=boto_session)

        cluster = Cluster(contact_points, ssl_context=ssl_context, auth_provider=auth_provider,
                          port=9142)
        self.session = cluster.connect(keyspace=keyspace_name)

Conecte-se à instância EC2 (usei o Gerenciador de Sessões), e execute o seguinte script Shell para realizar essas tarefas:

  1. Instale o Git.
  2. Instale o Pythion3.
  3. Clone o repositório GitHub performance_db.
  4. Instale e ative o ambiente virtual Python3.
  5. Instale bibliotecas/dependências de terceiros.
  6. Execute cada caso de teste.
Shell

 

sudo yum install git
sudo yum install python3

git clone https://github.com/dshilman/db_performance.git
sudo git pull

cd db_performance
python3 -m venv venv
source ./venv/bin/activate

sudo python3 -m pip install -r requirements.txt

cd code
sudo python3 -m dynamo_db
sudo python3 -m cassandra_db
sudo python3 -m redis_db
sudo python3 -m mongo_db

Você deve ver a seguinte saída para os dois primeiros casos de teste:

(venv) sh-5.2$ sudo python3 -m dynamo_db

Dados de Desempenho:

Create Time  Read Time

1          0.336909   0.031491

2          0.056884   0.053334

3       0.085881   0.031385

4          0.084940   0.050059

5          0.169012   0.050044

..              …        …

916        0.047431   0.041877

917        0.043795   0.024649

918        0.075325   0.035251

919        0.101007   0.068767

920        0.103432   0.037742

 

[200 linhas x 2 colunas]

Média do Create Time: 0.0858926808834076, desvio padrão: 0.07714510154026173

Média do Read Time: 0.04880355834960937, desvio padrão: 0.028805479258627295

Tempo de execução: 11.499964714050293

(venv) sh-5.2$ sudo python3 -m cassandra_db

Dados de Desempenho:

     Create Time  Read Time

1          0.024815   0.005986

2          0.008256   0.006927

3          0.008996   0.009810

4          0.005362   0.005892

5          0.010117   0.010308

..              …        …

916        0.006234   0.008147

917        0.011564   0.004347

918     0.007857      0.008329

919        0.007260   0.007370

920        0.004654   0.006049

 

[200 linhas x 2 colunas]

Média do Tempo de Criação: 0.009145524501800537, desvio padrão: 0.005201661271831082

Média do Tempo de Leitura: 0.007248317003250122, desvio padrão: 0.003557610695674452

Tempo de Execução: 1.6279327869415283

Resultados dos Testes

DynamoDB Cassandra MongoDB Redis
Create mean: 0.0859
stdev: 0.0771
mean: 0.0091
stdev: 0.0052
mean: 0.0292
std: 0.0764
mean: 0.0028
stdev: 0.0049
Read mean:  0.0488
stdev: 0.0288
mean: 0.0072
stdev: 0.0036
mean: 0.0509
std: 0.0027
mean: 0.0012
stdev: 0.0016
Exec Time 11.45 sec 1.6279 sec 10.2608 sec 0.3465 sec

Minhas Observações

  • I was blown away by Cassandra’s fast performance. Cassandra support for SQL allows rich access pattern queries and AWS Keyspaces offer cross-region replication.
  • I find DynamoDB’s performance disappointing despite the AWS hype about it. You should try to avoid the cross-partition table scan and thus must use an index for each data access pattern. DynamoDB global tables enable cross-region data replication.
  • MongoDB possui um SDK muito simples, é divertido de usar e tem o melhor suporte para o tipo de dados JSON. Você pode criar índices e executar consultas complexas em atributos JSON aninhados. Com o surgimento de novos formatos de dados binários, o MongoDB pode perder seu apelo.
  • Redis tem um desempenho incrivelmente rápido, no entanto, no final do dia, é uma cache de chave/valor, mesmo que suporte tipos de dados complexos. O Redis oferece recursos poderosos, como pipelining e scripting, para melhorar ainda mais o desempenho das consultas, passando código para o Redis executar no lado do servidor.

Conclusão

Em conclusão, escolher o banco de dados NoSQL gerenciado pela AWS para sua plataforma de dados de referência empresarial depende das suas prioridades específicas. Se o desempenho e a replicação entre regiões são sua principal preocupação, a AWS Cassandra se destaca como uma vencedora clara. O DynamoDB se integra bem a outros serviços da AWS, como Lambda e Kinesis, e, portanto, é uma ótima opção para arquitetura nativa da AWS ou sem servidor. Para aplicativos que exigem suporte robusto para tipos de dados JSON, o MongoDB assume a liderança. No entanto, se seu foco é em rastreamento rápido ou gerenciamento de sessão para alta disponibilidade, o Redis prova ser uma excelente opção. Finalmente, a decisão deve estar alinhada com as necessidades únicas de sua organização.

Como sempre, você pode encontrar o código no repositório do GitHub vinculado anteriormente neste artigo (veja a tarefa do script Shell #3 acima). Sinta-se à vontade para me contatar se precisar de ajuda para executar este código ou com a configuração do AWS.

Source:
https://dzone.com/articles/aws-nosql-performance-lab-using-python