Amazon QLDB 司機推薦 - Amazon Quantum Ledger 資料庫 (Amazon QLDB)

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

Amazon QLDB 司機推薦

重要

支援結束通知:現有客戶將能夠使用 Amazon,QLDB直到 2025 年 7 月 31 日終止支援為止。有關更多詳細信息,請參閱將 Amazon QLDB 分類帳遷移到 Amazon Aurora 郵政. SQL

本節說明針對任何支援的語言設定和使用 Amazon QLDB 驅動程式的最佳實務。所提供的程式碼範例專為 Java 而設。

這些建議適用於大多數典型的使用案例,但一種大小並不適合所有使用案例。根據您認為適合您的應用程式,請使用下列建議。

配置對 QldbDriver 象

QldbDriver對象通過維護跨交易重複使用的會話集區來管理與分類帳的連接。會表示到分類帳的單個連接。QLDB每個會話支持一個主動運行的事務。

重要

對於較舊的QldbDriver驅動程序版本,會話池功能仍在PooledQldbDriver對像中而不是。如果您使用下列其中一個版本,請在本主題的QldbDriver其餘部分中取代任何提及的內容。PooledQldbDriver

驅動程式 版本
Java 1.1.0或更早版本
.NET 0.1.0-beta
Node.js 1.0.0-rc.1或更早版本
Python 2.0.2或更早版本

PooledQldbDriver對象在最新版本的驅動程序中被棄用。我們建議您升級至最新版本,並將的任何執行個體轉換PooledQldbDriverQldbDriver

配置 QldbDriver 為全域物件

若要最佳化驅動程式和工作階段的使用,請確定應用程式執行個體中只有一個驅動程式的全域執行個體。例如在 Java 中,您可以使用依賴注入框架,如 S pring谷歌 Guice匕首。下面的代碼示例演示了如何配置QldbDriver為單例。

@Singleton public QldbDriver qldbDriver (AWSCredentialsProvider credentialsProvider, @Named(LEDGER_NAME_CONFIG_PARAM) String ledgerName) { QldbSessionClientBuilder builder = QldbSessionClient.builder(); if (null != credentialsProvider) { builder.credentialsProvider(credentialsProvider); } return QldbDriver.builder() .ledger(ledgerName) .transactionRetryPolicy(RetryPolicy .builder() .maxRetries(3) .build()) .sessionClientBuilder(builder) .build(); }

設定重試嘗試

當發生常見的暫時性例外狀況 (例如SocketTimeoutExceptionNoHttpResponseException) 時,驅動程式會自動重試交易。若要設定重試嘗試的次數上限,您可以在建立的執行個體時使用transactionRetryPolicy組態物件的maxRetries參數QldbDriver。(如果是上一節所列的舊版驅動程式版本,請使用的retryLimit參數PooledQldbDriver。)

maxRetries 的預設值為 4

用戶端錯誤 (例如InvalidParameterException無法重試)。當它們發生時,事務被中止,會話返回到池,並將異常拋出到驅動程序的客戶端。

設定同時工作階段和交易的最大數目

執行環境用QldbDriver來執行交易的分類帳階段作業數目上限是由其maxConcurrentTransactions參數所定義。(對於上一節所列的舊版驅動程式版本,這是由的poolLimit參數定義PooledQldbDriver。)

此限制必須大於零且小於或等於工作階段用戶端允許的開啟HTTP連線數目上限 (如具體的定義) AWS SDK。例如,在 Java 中,在ClientConfiguration對象中設置了最大連接數。

的預設值maxConcurrentTransactions是您的最大連線設定 AWS SDK。

在應用程式QldbDriver中設定時,請考慮下列擴展考量:

  • 您的集區至少應該擁有與您計劃擁有的同時執行交易數量相同的工作階段數目。

  • 在主管執行緒委派給工作者執行緒的多執行緒模型中,驅動程式的工作階段至少應該與工作者執行緒數目相同。否則,在尖峰負載時,執行緒將會等候可用的工作階段。

  • 在中定義每個分類帳並行作用中階段作業的服務限制Amazon 的配額和限制 QLDB。請確定您設定的同時工作階段限制不超過所有用戶端的單一分類帳所使用的同時工作階段限制。

重試例外

重試中發生的例外狀況時QLDB,請考慮下列建議。

重試 OccConflictException

交易所存取的資料自交易開始以來已變更時,就會發生最佳並行控制 (OCC) 衝突例外狀況。QLDB嘗試提交交易時拋出此異常。驅動程式會根據設定的次數重試交易。maxRetries

如需使用索引來限制OCC衝突的詳細資訊OCC和最佳作法,請參閱Amazon QLDB 并发模型

重試以外的其他例外 QldbDriver

若要在執行階段期間擲回自訂、應用程式定義的例外狀況時,重試驅動程式外部的交易,您必須封裝交易。例如,在 Java 中,下面的代碼演示了如何使用 Reslience4j 庫重試中的事務。QLDB

private final RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(MAX_RETRIES) .intervalFunction(IntervalFunction.ofExponentialRandomBackoff()) // Retry this exception .retryExceptions(InvalidSessionException.class, MyRetryableException.class) // But fail for any other type of exception extended from RuntimeException .ignoreExceptions(RuntimeException.class) .build(); // Method callable by a client public void myTransactionWithRetries(Params params) { Retry retry = Retry.of("registerDriver", retryConfig); Function<Params, Void> transactionFunction = Retry.decorateFunction( retry, parameters -> transactionNoReturn(params)); transactionFunction.apply(params); } private Void transactionNoReturn(Params params) { try (driver.execute(txn -> { // Transaction code }); } return null; }
注意

在QLDB驅動程式之外重試交易會產生乘數效應。例如,如果設定QldbDriver為重試三次,而自訂重試邏輯也會重試三次,則相同的交易最多可重試九次。

使交易冪等

作為最佳實踐,使寫入事務具有冪等性,以避免在重試的情況下出現任何意外的副作用。如果事務可以運行多次並每次產生相同的結果,則事務是冪等的。

如需進一步了解,請參閱Amazon QLDB 并发模型

最佳化效能

若要在使用驅動程式執行交易時最佳化效能,請考量下列事項:

  • execute作業永遠至少會SendCommandAPI呼叫三次QLDB,包括下列命令:

    1. StartTransaction

    2. ExecuteStatement

      這個命令會針對您在區execute塊中執行的每個 PartiQL 陳述式叫用。

    3. CommitTransaction

    考慮計算應用程式的整體工作負載時所進行的API呼叫總數。

  • 一般而言,我們建議您從單一執行緒寫入器開始,並透過在單一交易中批次處理多個陳述式來最佳化交易。根據中的定義,將交易大小、文件大小和每筆交易的文件數量配額最大化Amazon 的配額和限制 QLDB

  • 如果批次處理不足以處理大型交易負載,您可以透過新增額外的寫入器來嘗試多執行緒。但是,您應該仔細考慮文件和交易順序的申請要求以及其他複雜性。

每個交易執行多個陳述式

上一節所述,您可以針對每個交易執行多個陳述式,以最佳化應用程式的效能。在下列程式碼範例中,您會查詢資料表,然後在交易中更新該資料表中的文件。您可以通過將 lambda 表達式傳遞給操作來完成此execute操作。

Java
// 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. public static boolean InsureCar(QldbDriver qldbDriver, final String vin) { final IonSystem ionSystem = IonSystemBuilder.standard().build(); final IonString ionVin = ionSystem.newString(vin); return qldbDriver.execute(txn -> { Result result = txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (!result.isEmpty()) { txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }
.NET
// 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. public static async Task<bool> InsureVehicle(IAsyncQldbDriver driver, string vin) { ValueFactory valueFactory = new ValueFactory(); IIonValue ionVin = valueFactory.NewString(vin); return await driver.Execute(async txn => { // Check if the vehicle is insured. Amazon.QLDB.Driver.IAsyncResult result = await txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (await result.CountAsync() > 0) { // If the vehicle is not insured, insure it. await txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }
Go
// 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. func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) { insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) if err != nil { return false, err } hasNext := result.Next(txn) if !hasNext && result.Err() != nil { return false, result.Err() } if hasNext { _, err = txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) if err != nil { return false, err } return true, nil } return false, nil }) if err != nil { panic(err) } return insured.(bool), err }
Node.js
// 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. async function insureCar(driver: QldbDriver, vin: string): Promise<boolean> { return await driver.executeLambda(async (txn: TransactionExecutor) => { const results: dom.Value[] = (await txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)).getResultList(); if (results.length > 0) { await txn.execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin); return true; } return false; }); };
Python
# 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))

驅動程序的execute操作隱式啟動會話和該會話中的事務。您在 lambda 運算式中執行的每個陳述式都會包裝在交易中。執行所有陳述式之後,驅動程式會自動提交交易。如果在自動重試限制用盡之後有任何陳述式失敗,就會中止交易。

在交易中傳播例外

當每個交易執行多個陳述式時,我們通常不建議您在交易中 catch 並吞下例外狀況。

例如,在 Java 中,下列程式會擷取的任何執行個體RuntimeException、記錄錯誤並繼續。這個程式碼範例被認為是不好的做法,因為即使UPDATE陳述式失敗,交易也會成功。因此,用戶端可能會假設更新沒有成功。

警告

請勿使用此程式碼範例。它提供了一個被認為是不好的做法的反模式示例。

// DO NOT USE this code example because it is considered bad practice public static void main(final String... args) { ConnectToLedger.getDriver().execute(txn -> { final Result selectTableResult = txn.execute("SELECT * FROM Vehicle WHERE VIN ='123456789'"); // Catching an error inside the transaction is an anti-pattern because the operation might // not succeed. // In this example, the transaction succeeds even when the update statement fails. // So, the client might assume that the update succeeded when it didn't. try { processResults(selectTableResult); String model = // some code that extracts the model final Result updateResult = txn.execute("UPDATE Vehicle SET model = ? WHERE VIN = '123456789'", Constants.MAPPER.writeValueAsIonValue(model)); } catch (RuntimeException e) { log.error("Exception when updating the Vehicle table {}", e.getMessage()); } }); log.info("Vehicle table updated successfully."); }

傳播(冒泡)異常。如果交易的任何部分失敗,讓execute作業中止交易,以便用戶端可以相應地處理例外狀況。