Laboratorio de Rendimiento de NoSQL en AWS con Python

En la mayoría de las firmas financieras, el procesamiento de transacciones en línea (OLTP) suele basarse en datos estáticos o poco actualizados, también conocidos como datos de referencia. Las fuentes de datos de referencia no siempre requieren capacidades de transacción ACID, sino que necesitan apoyo para consultas de lectura rápida a menudo basadas en patrones de acceso de datos simples y arquitectura orientada a eventos para garantizar que los sistemas objetivo permanezcan actualizados. Bases de datos NoSQL surgen como candidatos ideales para cumplir con estos requisitos, y las plataformas en la nube como AWS ofrecen ecosistemas de datos gestionados y altamente resilientes.

En este artículo, no voy a determinar cuál base de datos NoSQL de AWS es mejor: el concepto de una base de datos mejor solo existe dentro de un contexto específico y con un propósito determinado. Compartiré un laboratorio de codificación para medir el rendimiento de las bases de datos NoSQL gestionadas por AWS, como DynamoDB, Cassandra, Redis y MongoDB.

Pruebas de Rendimiento

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

Carga JSON

La clase base/parent en base_db.py implementa la lógica del caso de prueba de ejecutar 10 hilos concurrentes para crear y leer 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 hilo ejecuta la rutina de escritura/lectura en el create_records y read_records, respectivamente. Tenga en cuenta que estas funciones no incluyen ninguna lógica específica de base de datos, sino que miden el rendimiento de cada ejecución de lectura y escritura.

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

Una vez ejecutado el caso de prueba, la función print_stats imprime las métricas de ejecución, como la media de lectura/escritura y los valores de desviación estándar (stdev), que indican el rendimiento y la consistencia de la base de datos (una stdev menor implica un rendimiento de ejecución más consistente).

Python

 

def print_stats(self):

  if len(self.performance_data) > 0:
    # Crear un DataFrame de Pandas a partir de los datos de rendimiento
    df = pd.DataFrame.from_dict(self.performance_data, orient='index')

    if not df.empty:
      df.sort_index(inplace=True)
      # Calcular la media y la desviación estándar para cada columna
      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

A diferencia de las bases de datos relacionales que admiten SQL estándar, cada base de datos NoSQL tiene su propio SDK. Las clases de casos de prueba secundarios para cada base de datos NoSQL solo necesitan implementar un constructor y las funciones create_record/read_recod que contienen el SDK de base de datos propietario para crear una conexión con la base de datos y crear/leer registros en unas pocas líneas de código.

Caso de Prueba de 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()

Configuración de AWS

Para ejecutar estos casos de prueba de rendimiento en una cuenta de AWS, debe seguir estos pasos:

  1. Crear un rol de IAM de EC2 con privilegios para acceder a los servicios de datos de AWS requeridos.
  2. Iniciar una instancia de EC2 y asignar el rol de IAM recién creado.
  3. Crear cada instancia de base de datos NoSQL.

Rol de IAM

Tabla de DynamoDB

Espacio de claves/Tabla de Cassandra

Tenga en cuenta que el host y las credenciales de la base de datos fueron codificados de forma rígida y eliminados en los módulos mongo_db.py y redis_db.py y necesitarán ser actualizados con la configuración de conexión de la base de datos correspondiente para su cuenta de AWS. Para conectarse a DynamoDB y Cassandra, opté por usar las credenciales de la sesión de Boto3 temporalmente asignadas al Rol de IAM db_performnace_iam_role. Este código se ejecutará en cualquier cuenta de AWS en la región Este 1 sin ninguna modificación.

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()

        # Configuración de Espacios de Claves de 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)

Conéctese a la instancia de EC2 (utilicé el Gestor de Sesión), y ejecute el siguiente script de Shell para realizar estas tareas:

  1. Instalar Git.
  2. Instalar Pythion3.
  3. Clonar el repositorio de GitHub performance_db.
  4. Instalar y activar el entorno virtual de Python3.
  5. Instalar bibliotecas/dependencias de terceros.
  6. Ejecutar cada caso de prueba.
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

Debería ver la siguiente salida para los dos primeros casos de prueba:

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

Datos de Rendimiento:

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 filas x 2 columnas]

Media de Create Time: 0.0858926808834076, desv. est.: 0.07714510154026173

Media de Read Time: 0.04880355834960937, desv. est.: 0.028805479258627295

Tiempo de ejecución: 11.499964714050293

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

Datos de rendimiento:

     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 filas x 2 columnas]

Tiempo de creación promedio: 0.009145524501800537, desv. est: 0.005201661271831082

Tiempo de lectura promedio: 0.007248317003250122, desv. est: 0.003557610695674452

Tiempo de ejecución: 1.6279327869415283

Resultados de la prueba

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

Mis observaciones

  • 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 tiene un SDK muy simple, es divertido de usar y tiene el mejor soporte para el tipo de datos JSON. Puedes crear índices y ejecutar consultas complejas en atributos JSON anidados. A medida que surgen nuevos formatos de datos binarios, MongoDB podría perder su atractivo.
  • Redis tiene un rendimiento sorprendentemente rápido, sin embargo, al final del día, es una caché de clave/valor, incluso si soporta tipos de datos complejos. Redis ofrece características poderosas como la canalización y la ejecución de scripts para mejorar aún más el rendimiento de las consultas al enviar código a Redis para que lo ejecute en el servidor.

Conclusión

En conclusión, elegir la base de datos NoSQL administrada por AWS para tu plataforma de datos de referencia empresarial depende de tus prioridades específicas. Si el rendimiento y la replicación entre regiones son tu principal preocupación, AWS Cassandra se destaca como un claro ganador. DynamoDB se integra bien con otros servicios de AWS como Lambda y Kinesis y, por lo tanto, es una excelente opción para arquitecturas nativas de AWS o sin servidor. Para aplicaciones que requieren un soporte robusto para tipos de datos JSON, MongoDB toma la delantera. Sin embargo, si tu enfoque es en la búsqueda rápida o la administración de sesiones para alta disponibilidad, Redis demuestra ser una excelente opción. En última instancia, la decisión debe alinearse con las necesidades únicas de tu organización.

Como siempre, puedes encontrar el código en el repositorio de GitHub vinculado anteriormente en este artículo (ver tarea del script de Shell #3 arriba). No dudes en contactarme si necesitas ayuda para ejecutar este código o con la configuración de AWS.

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