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 la versión básicacrear, leer, actualizar y eliminar(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 singularidad.

nota

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

Importación del controlador

En el siguiente ejemplo de código se 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 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 instancias del controlador

En el siguiente ejemplo de código se crea una instancia del controlador que se conecta a un nombre de libro mayor 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

Ejecuciones QLDBcrear, leer, actualizar y eliminar(CRUD) como parte de una transacción.

aviso

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

Realización de transacciones idempotentes

Le recomendamos que haga que las transacciones de escritura sean idempotentes para evitar efectos secundarios inesperados en caso de reintentos. Una transacción esidempotentesi 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. En primer lugar, la transacción debe comprobar si el documento ya existe en la tabla o no. Sin esta comprobación, la tabla podría terminar con documentos duplicados.

Supongamos que QLDB confirma correctamente la transacción en el lado del servidor, pero el cliente se agota el tiempo de espera mientras espera una respuesta. Si la transacción no es idempotente, el mismo documento podría insertarse más de una vez en el caso de un reintento.

Uso de índices para evitar análisis completos de tablas

También le recomendamos que ejecute extractos con unaWHEREcláusula predicado mediante unigualdadoperador en un campo indexado o un ID de documento; por ejemplo,WHERE indexedField = 123oWHERE indexedField IN (456, 789). Sin esta búsqueda indexada, QLDB necesita realizar un análisis de tabla, lo que puede provocar tiempos de espera de transacción ocontrol de concurrencia optimista(OCC) conflictos.

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

Transacciones creadas implícitamente

Lapyqldb.driver.qldb_driver.execute_lambdaacepta una función lambda que recibe una instancia depyqldb.execution.executor.Executor, que puede utilizar para ejecutar sentencias. La instancia deExecutorenvuelve una transacción creada implícitamente.

Puede ejecutar sentencias dentro de la función lambda mediante elexecute_statementmétodo del ejecutor de transacciones. El conductor confirma implícitamente la transacción cuando vuelve la función lambda.

nota

Laexecute_statementadmite tipos Amazon Ion y tipos nativos de Python. Si pasa un tipo nativo de Python como argumento aexecute_statement, el controlador lo convierte a un tipo Ion utilizando elamazon.ion.simpleionmódulo (siempre que se admita la conversión para el tipo de datos de Python dado). Para conocer los tipos de datos compatibles y las reglas de conversión, consulte laCódigo fuente SimpleIon.

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

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 índices

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

En el siguiente ejemplo de código se utiliza un parámetro de consulta de tipo nativo.

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

En el siguiente ejemplo de código se utiliza un parámetro de consulta de tipo Ion.

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

En el siguiente ejemplo de código se utilizan varios parámetros de consulta.

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

En el siguiente ejemplo de código se 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 ejecuta una consulta sin una búsqueda indexada, invoca un análisis de tabla completo. En este ejemplo, recomendamos que tenga unaíndiceen elGovIdpara optimizar el rendimiento. Sin índice enGovId, las consultas pueden tener más latencia y también pueden provocar excepciones de conflicto de OCC o tiempos de espera de transacción.

Inserción de documentos

En el siguiente ejemplo de código se insertan 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 Ion.

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 elPersontable. Antes de insertar, comprueba primero si el documento ya existe en la tabla. Esta comprobación hace que la transacción sea idempotente por naturaleza. Incluso si ejecuta esta transacción varias veces, no causará ningún efecto secundario no deseado.

nota

En este ejemplo, recomendamos que tenga un índice en laGovIdpara optimizar el rendimiento. Sin índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o tiempos de espera de transacción.

Inserción de varios documentos en una sola declaración

Para insertar varios documentos mediante un soloINSERT, puede pasar un parámetro de tipolistaa la declaración de la siguiente manera.

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

No incluye el marcador de posición variable (?) en soportes de doble ángulo (<<...>>) al pasar una lista. En las instrucciones manuales PartiQL, los corchetes angulares dobles indican una colección no ordenada conocida comobolsa.

Actualización de documentos

En el siguiente ejemplo de código se utilizan 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))

En el siguiente ejemplo de código se utilizan tipos de datos Ion.

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 que tenga un índice en laGovIdpara optimizar el rendimiento. Sin índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o tiempos de espera de transacción.

Eliminación de documentos

En el siguiente ejemplo de código se utilizan 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))

En el siguiente ejemplo de código se utilizan tipos de datos Ion.

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 que tenga un índice en laGovIdpara optimizar el rendimiento. Sin índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o tiempos de espera de transacción.

Ejecución de 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))

Lógica de reintento

El de los conductoresexecute_lambdatiene un mecanismo de reintento integrado que vuelve a intentar la transacción si se produce una excepción reintentable (como tiempos de espera o conflictos de OCC).

3.x

El número máximo de reintentos y la estrategia de reintento se pueden configurar.

El límite de reintentos predeterminado es4, y la estrategia de retroceso predeterminada esRetroceso exponencial y fluctuacióncon una base de10milisegundos. Puede establecer la configuración de reintento por instancia de controlador y también por transacción mediante una instancia depyqldb.config.retry_config.RetryConfig.

En el siguiente ejemplo de código se 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)

En el siguiente ejemplo de código se especifica la lógica de reintentos con un límite de reintentos personalizado y una estrategia de retroceso personalizada para una ejecución lambda concreta. Esta configuración paraexecute_lambdaanula 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 se puede configurar. Puede configurar el límite de reintentos estableciendo laretry_limitpropiedad al inicializarPooledQldbDriver.

El límite de reintentos predeterminado es4.

Implementación de restricciones de exclusividad

QLDB no admite índices únicos. Pero es fácil implementar este comportamiento en tu aplicación.

Supongamos que desea implementar una restricción de singularidad en laGovIden elPersontable. Para ello, puede escribir una transacción que realiza lo siguiente:

  1. Afirmar que la tabla no tiene documentos existentes con un determinadoGovId.

  2. Inserte el documento si se pasa la afirmación.

Si una transacción competidora supera simultáneamente la afirmación, solo una de las transacciones se comprometerá correctamente. La otra transacción fallará con una excepción de conflicto de OCC.

En el siguiente ejemplo de código se muestra cómo implementar esta lógica de restricciones de exclusividad.

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 que tenga un índice en laGovIdpara optimizar el rendimiento. Sin índice enGovId, las declaraciones pueden tener más latencia y también pueden dar lugar a excepciones de conflicto de OCC o tiempos de espera de transacción.

Trabajo con Amazon Ion

En las siguientes secciones se muestra cómo utilizar el módulo Amazon Ion para procesar datos Ion.

Importación del módulo Ion

import amazon.ion.simpleion as simpleion

Creación de tipos Ion

En el siguiente ejemplo de código se crea un objeto Ion a partir de texto 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

En el siguiente ejemplo de código se 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

Obtención de un volcado binario Ion

# 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'

Obtención de un volcado de texto 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 acerca de cómo trabajar con Ion, consulte laDocumentación de Amazon Ionen GitHub. Para obtener más ejemplos de código sobre cómo trabajar con Ion en QLDB, consulteTrabajo con tipos de datos de Amazon Ion en Amazon QLDB.