Controlador Amazon QLDB para Python: referencia de libro de cocina - Amazon Quantum Ledger Database (Amazon QLDB)

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Controlador Amazon QLDB para Python: referencia de libro de cocina

Esta guía de referencia muestra casos de uso comunes del controlador Amazon QLDB para Python. Proporciona ejemplos de código de Python que demuestran cómo usar el controlador para ejecutar operaciones básicas de creación, lectura, actualización y eliminación (CRUD). También incluye ejemplos de código para procesar datos de Amazon Ion. Además, esta guía destaca las mejores prácticas para hacer que las transacciones sean idempotentes e implementar restricciones de unicidad.

nota

Cuando proceda, algunos casos de uso tienen diferentes ejemplos de código para cada versión principal compatible del controlador QLDB para Python.

Importación del controlador

El siguiente ejemplo de código importa el controlador.

3.x
from pyqldb.driver.qldb_driver import QldbDriver import amazon.ion.simpleion as simpleion
2.x
from pyqldb.driver.pooled_qldb_driver import PooledQldbDriver import amazon.ion.simpleion as simpleion
nota

En este ejemplo también se importa el paquete Amazon Ion (amazon.ion.simpleion). Necesita este paquete para procesar los datos de Ion al ejecutar algunas operaciones de datos en esta referencia. Para obtener más información, consulte Trabajo con Amazon Ion.

Creación de una instancia del controlador

El siguiente ejemplo de código crea una instancia del controlador que se conecta a un nombre de registro especificado mediante la configuración predeterminada.

3.x
qldb_driver = QldbDriver(ledger_name='vehicle-registration')
2.x
qldb_driver = PooledQldbDriver(ledger_name='vehicle-registration')

Operaciones CRUD

QLDB ejecuta operaciones de creación, lectura, actualización y eliminación (CRUD) como parte de una transacción.

aviso

Como práctica recomendada, haga que sus transacciones de escritura sean estrictamente idempotentes.

Hacer que las transacciones sean idempotentes

Le recomendamos que haga que las transacciones de escritura sean idempotentes para evitar efectos secundarios inesperados en caso de reintentos. Una transacción es idempotente si puede ejecutarse varias veces y producir resultados idénticos cada vez.

Por ejemplo, considere una transacción que inserta un documento en una tabla denominadaPerson. La transacción debe comprobar primero si el documento ya existe o no en la tabla. Sin esta comprobación, la tabla podría terminar con documentos duplicados.

Supongamos que QLDB confirma correctamente la transacción en el servidor, pero el cliente agota el tiempo de espera mientras espera una respuesta. Si la transacción no es idempotente, se puede insertar el mismo documento más de una vez en caso de que se vuelva a intentar.

Uso de índices para evitar escaneos de tablas completas

También le recomendamos que ejecute sentencias con una cláusula deWHERE predicados mediante un operador de igualdad en un campo indexado o un ID de documento; por ejemplo,WHERE indexedField = 123 oWHERE indexedField IN (456, 789). Sin esta búsqueda indexada, QLDB necesita realizar un escaneo de tablas, lo que puede provocar tiempos de espera de las transacciones o conflictos optimistas en el control de la concurrencia (OCC).

Para obtener más información sobre la OCC, consulteModelo de simultaneidad de Amazon QLDB.

Transacciones creadas implícitamente

El método pyqldb.driver.qldb_driver.execute_lambda acepta una función lambda que recibe una instancia de PyQLDB.Execution.Executor.Executor, que puede usar para ejecutar sentencias. La instancia deExecutor envuelve una transacción creada de forma implícita.

Puede ejecutar sentencias dentro de la función lambda mediante el método execute_statement del ejecutor de transacciones. El controlador confirma implícitamente la transacción cuando la función lambda retorna.

nota

Elexecute_statement método admite los tipos de Amazon Ion y los tipos nativos de Python. Si pasas un tipo nativo de Python como argumento aexecute_statement, el controlador lo convierte en un tipo Ion mediante elamazon.ion.simpleion módulo (siempre que se admita la conversión para el tipo de datos de Python dado). Para conocer los tipos de datos y las reglas de conversión compatibles, consulte el código fuente de simpleion.

En las siguientes secciones se muestra cómo ejecutar operaciones CRUD básicas, especificar una lógica de reintentos personalizada e implementar restricciones de unicidad.

Creación de tablas

def create_table(transaction_executor): transaction_executor.execute_statement("CREATE TABLE Person") qldb_driver.execute_lambda(lambda executor: create_table(executor))

Creación de índice

def create_index(transaction_executor): transaction_executor.execute_statement("CREATE INDEX ON Person(GovId)") qldb_driver.execute_lambda(lambda executor: create_index(executor))

Lectura de documentos

# Assumes that Person table has documents as follows: # { "GovId": "TOYENC486FH", "FirstName": "Brent" } def read_documents(transaction_executor): cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'") for doc in cursor: print(doc["GovId"]) # prints TOYENC486FH print(doc["FirstName"]) # prints Brent qldb_driver.execute_lambda(lambda executor: read_documents(executor))

Uso de parámetros de consulta

El siguiente ejemplo de código utiliza un parámetro de consulta de tipo nativo.

cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH')

El siguiente ejemplo de código utiliza un parámetro de consulta de tipo Ion.

name = ion.loads('Brent') cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE FirstName = ?", name)

El siguiente ejemplo de código utiliza varios parámetros de consulta.

cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", 'TOYENC486FH', "Brent")

El siguiente ejemplo de código utiliza una lista de parámetros de consulta.

gov_ids = ['TOYENC486FH','ROEE1','YH844'] cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId IN (?,?,?)", *gov_ids)
nota

Cuando se ejecuta una consulta sin una búsqueda indexada, se invoca un escaneo completo de la tabla. En este ejemplo, recomendamos tener un índice en elGovId campo para optimizar el rendimiento. Sin un índice activadoGovId, las consultas pueden tener más latencia y también provocar excepciones de conflictos de OCC o tiempos de espera para las transacciones.

Inserción de documentos

El siguiente ejemplo de código inserta tipos de datos nativos.

def insert_documents(transaction_executor, arg_1): # Check if doc with GovId:TOYENC486FH exists # This is critical to make this transaction idempotent cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH') # Check if there is any record in the cursor first_record = next(cursor, None) if first_record: # Record already exists, no need to insert pass else: transaction_executor.execute_statement("INSERT INTO Person ?", arg_1) doc_1 = { 'FirstName': "Brent", 'GovId': 'TOYENC486FH', } qldb_driver.execute_lambda(lambda executor: insert_documents(executor, doc_1))

En el siguiente ejemplo de código se insertan tipos de datos de iones.

def insert_documents(transaction_executor, arg_1): # Check if doc with GovId:TOYENC486FH exists # This is critical to make this transaction idempotent cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", 'TOYENC486FH') # Check if there is any record in the cursor first_record = next(cursor, None) if first_record: # Record already exists, no need to insert pass else: transaction_executor.execute_statement("INSERT INTO Person ?", arg_1) doc_1 = { 'FirstName': 'Brent', 'GovId': 'TOYENC486FH', } # create a sample Ion doc ion_doc_1 = simpleion.loads(simpleion.dumps(doc_1))) qldb_driver.execute_lambda(lambda executor: insert_documents(executor, ion_doc_1))

Esta transacción inserta un documento en laPerson tabla. Antes de insertarlo, comprueba primero si el documento ya existe en la tabla. Esta comprobación hace que la transacción sea de naturaleza idempotente. Incluso si realizas esta transacción varias veces, no causará ningún efecto secundario no deseado.

nota

En este ejemplo, recomendamos tener un índice en elGovId campo para optimizar el rendimiento. Sin un índice activadoGovId, las declaraciones pueden tener más latencia y también pueden provocar excepciones de conflictos de OCC o tiempos de espera para las transacciones.

Insertar varios documentos en una declaración

Para insertar varios documentos mediante una solaINSERT sentencia, puede pasar un parámetro de tipo lista a la sentencia de la siguiente manera.

# people is a list transaction_executor.execute_statement("INSERT INTO Person ?", people)

No debe incluir el marcador de posición variable (?) entre corchetes de doble ángulo (<<...>>) al pasar una lista. En las sentencias PartiQL manuales, los corchetes de doble ángulo indican una colección desordenada conocida como bolsa.

Actualización de documentos

El siguiente ejemplo de código utiliza tipos de datos nativos.

def update_documents(transaction_executor, gov_id, name): transaction_executor.execute_statement("UPDATE Person SET FirstName = ? WHERE GovId = ?", name, gov_id) gov_id = 'TOYENC486FH' name = 'John' qldb_driver.execute_lambda(lambda executor: update_documents(executor, gov_id, name))

El siguiente ejemplo de código utiliza tipos de datos de iones.

def update_documents(transaction_executor, gov_id, name): transaction_executor.execute_statement("UPDATE Person SET FirstName = ? WHERE GovId = ?", name, gov_id) # Ion datatypes gov_id = simpleion.loads('TOYENC486FH') name = simpleion.loads('John') qldb_driver.execute_lambda(lambda executor: update_documents(executor, gov_id, name))
nota

En este ejemplo, recomendamos tener un índice en elGovId campo para optimizar el rendimiento. Sin un índice activadoGovId, las declaraciones pueden tener más latencia y también pueden provocar excepciones de conflictos de OCC o tiempos de espera para las transacciones.

Eliminación de documentos

El siguiente ejemplo de código utiliza tipos de datos nativos.

def delete_documents(transaction_executor, gov_id): cursor = transaction_executor.execute_statement("DELETE FROM Person WHERE GovId = ?", gov_id) gov_id = 'TOYENC486FH' qldb_driver.execute_lambda(lambda executor: delete_documents(executor, gov_id))

El siguiente ejemplo de código utiliza tipos de datos de iones.

def delete_documents(transaction_executor, gov_id): cursor = transaction_executor.execute_statement("DELETE FROM Person WHERE GovId = ?", gov_id) # Ion datatypes gov_id = simpleion.loads('TOYENC486FH') qldb_driver.execute_lambda(lambda executor: delete_documents(executor, gov_id))
nota

En este ejemplo, recomendamos tener un índice en elGovId campo para optimizar el rendimiento. Sin un índice activadoGovId, las declaraciones pueden tener más latencia y también pueden provocar excepciones de conflictos de OCC o tiempos de espera para las transacciones.

Ejecutar varios estados de cuenta en una transacción

# This code snippet is intentionally trivial. In reality you wouldn't do this because you'd # set your UPDATE to filter on vin and insured, and check if you updated something or not. def do_insure_car(transaction_executor, vin): cursor = transaction_executor.execute_statement( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) first_record = next(cursor, None) if first_record: transaction_executor.execute_statement( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) return True else: return False def insure_car(qldb_driver, vin_to_insure): return qldb_driver.execute_lambda( lambda executor: do_insure_car(executor, vin_to_insure))

Reintentar la lógica

Elexecute_lambda método del conductor tiene un mecanismo de reintento incorporado que reintenta la transacción si se produce una excepción que se puede volver a intentar (como tiempos de espera o conflictos de OCC).

3.x

El número máximo de reintentos y la estrategia de reintentos son configurables.

El límite de reintentos predeterminado es4, y la estrategia de retroceso predeterminada es Exponential Backoff and Jitter con una base de10 milisegundos. Puede establecer la configuración de reintentos por instancia de controlador y también por transacción mediante una instancia de pyqldb.config.retry_config. RetryConfig.

El siguiente ejemplo de código especifica la lógica de reintentos con un límite de reintentos personalizado y una estrategia de retroceso personalizada para una instancia del controlador.

from pyqldb.config.retry_config import RetryConfig from pyqldb.driver.qldb_driver import QldbDriver # Configuring retry limit to 2 retry_config = RetryConfig(retry_limit=2) qldb_driver = QldbDriver("test-ledger", retry_config=retry_config) # Configuring a custom backoff which increases delay by 1s for each attempt. def custom_backoff(retry_attempt, error, transaction_id): return 1000 * retry_attempt retry_config_custom_backoff = RetryConfig(retry_limit=2, custom_backoff=custom_backoff) qldb_driver = QldbDriver("test-ledger", retry_config=retry_config_custom_backoff)

El siguiente ejemplo de código especifica la lógica de reintentos con un límite de reintentos personalizado y una estrategia de retroceso personalizada para una ejecución lambda en particular. Esta configuraciónexecute_lambda anula la lógica de reintento establecida para la instancia del controlador.

from pyqldb.config.retry_config import RetryConfig from pyqldb.driver.qldb_driver import QldbDriver # Configuring retry limit to 2 retry_config_1 = RetryConfig(retry_limit=4) qldb_driver = QldbDriver("test-ledger", retry_config=retry_config_1) # Configuring a custom backoff which increases delay by 1s for each attempt. def custom_backoff(retry_attempt, error, transaction_id): return 1000 * retry_attempt retry_config_2 = RetryConfig(retry_limit=2, custom_backoff=custom_backoff) # The config `retry_config_1` will be overriden by `retry_config_2` qldb_driver.execute_lambda(lambda txn: txn.execute_statement("CREATE TABLE Person"), retry_config_2)
2.x

El número máximo de reintentos es configurable. Puede configurar el límite de reintentos configurando laretry_limit propiedad al inicializarPooledQldbDriver.

El límite de reintentos predeterminado es4.

Implementación de restricciones de unicidad

QLDB no admite índices únicos, pero puede implementar este comportamiento en su aplicación.

Supongamos que desea implementar una restricción de unicidad en elGovId campo de laPerson tabla. Para ello, puede escribir una transacción que haga lo siguiente:

  1. Afirma que la tabla no tiene ningún documento existente con una especificadaGovId.

  2. Inserte el documento si la afirmación es aprobada.

Si una transacción competidora supera la afirmación simultáneamente, solo una de las transacciones se confirmará satisfactoriamente. La otra transacción fallará si se produce una excepción de conflicto de OCC.

En el ejemplo de código siguiente se muestra cómo implementar esta lógica de restricción de unicidad.

def insert_documents(transaction_executor, gov_id, document): # Check if doc with GovId = gov_id exists cursor = transaction_executor.execute_statement("SELECT * FROM Person WHERE GovId = ?", gov_id) # Check if there is any record in the cursor first_record = next(cursor, None) if first_record: # Record already exists, no need to insert pass else: transaction_executor.execute_statement("INSERT INTO Person ?", document) qldb_driver.execute_lambda(lambda executor: insert_documents(executor, gov_id, document))
nota

En este ejemplo, recomendamos tener un índice en elGovId campo para optimizar el rendimiento. Sin un índice activadoGovId, las declaraciones pueden tener más latencia y también pueden provocar excepciones de conflictos de OCC o tiempos de espera para las transacciones.

Trabajo con Amazon Ion

Las siguientes secciones muestran cómo usar el módulo Amazon Ion para procesar datos de Ion.

Importación del módulo Ion

import amazon.ion.simpleion as simpleion

Creación de tipos de Ion

El siguiente ejemplo de código crea un objeto Ion a partir del texto de Ion.

ion_text = '{GovId: "TOYENC486FH", FirstName: "Brent"}' ion_obj = simpleion.loads(ion_text) print(ion_obj['GovId']) # prints TOYENC486FH print(ion_obj['Name']) # prints Brent

El siguiente ejemplo de código crea un objeto Ion a partir de Pythondict.

a_dict = { 'GovId': 'TOYENC486FH', 'FirstName': "Brent" } ion_obj = simpleion.loads(simpleion.dumps(a_dict)) print(ion_obj['GovId']) # prints TOYENC486FH print(ion_obj['FirstName']) # prints Brent

Obtener un volcado binario de iones

# ion_obj is an Ion struct print(simpleion.dumps(ion_obj)) # b'\xe0\x01\x00\xea\xee\x97\x81\x83\xde\x93\x87\xbe\x90\x85GovId\x89FirstName\xde\x94\x8a\x8bTOYENC486FH\x8b\x85Brent'

Obtener un volcado de texto de Ion

# ion_obj is an Ion struct print(simpleion.dumps(ion_obj, binary=False)) # prints $ion_1_0 {GovId:'TOYENC486FH',FirstName:"Brent"}

Para obtener más información sobre cómo trabajar con Ion, consulte la documentación de Amazon Ion en GitHub. Para obtener más ejemplos de código sobre cómo trabajar con Ion en QLDB, consulteTrabajando con tipos de datos de Amazon Ion en Amazon QLDB.