Getting PartiQL statement statistics - Amazon Quantum Ledger Database (Amazon QLDB)

Getting PartiQL statement statistics

Amazon QLDB provides statement execution statistics that can help you optimize your usage of QLDB by running more efficient PartiQL statements. QLDB returns these statistics along with the results of the statement. They include metrics that quantify consumed I/O usage and server-side processing time, which enable you to identify inefficient statements.

This feature is currently available in the PartiQL editor on the QLDB console, the QLDB shell, and the latest version of the QLDB driver for all supported languages. You can also view statement statistics for your query history on the console.

I/O usage

The I/O usage metric describes the number of read I/O requests. If the number of read I/O requests is higher than expected, it indicates that the statement isn't optimized, such as the lack of an index. We recommend that you review Optimal query patterns in the previous topic, Optimizing query performance.

Note

When you run a CREATE INDEX statement on a non-empty table, the I/O usage metric includes read requests for the synchronous index creation call only.

QLDB builds the index for any existing documents in the table asynchronously. These asynchronous read requests aren't included in the I/O usage metric from your statement results. Asynchronous read requests are charged separately and are added to your total read I/Os after the index build is completed.

Using the QLDB console

To get a statement's read I/O usage by using the QLDB console, do the following steps:

  1. Open the Amazon QLDB console at https://console.aws.amazon.com/qldb.

  2. In the navigation pane, choose PartiQL editor.

  3. Choose a ledger from the dropdown list of ledgers.

  4. In the query editor window, enter any statement of your choice, and then choose Run. The following is a query example.

    SELECT * FROM testTable WHERE firstName = 'Jim'

    To run a statement, you can also use the keyboard shortcut Ctrl+Enter for Windows, or Cmd+Return for macOS. For more keyboard shortcuts, see PartiQL editor keyboard shortcuts.

  5. Below the query editor window, your query results include read I/Os, which is the number of read requests that were made by the statement.

You can also view the read I/Os of your query history by doing the following steps:

  1. In navigation pane, choose Recent queries under PartiQL editor.

  2. The Read I/Os column displays the number of read requests that were made by each statement.

Using the QLDB driver

To get a statement's I/O usage by using the QLDB driver, call the getConsumedIOs operation of the result's stream cursor or buffered cursor.

The following code examples show how to get read I/Os from the stream cursor of a statement result.

Java
import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.qldb.IOUsage; import software.amazon.qldb.Result; IonSystem ionSystem = IonSystemBuilder.standard().build(); IonValue ionFirstName = ionSystem.newString("Jim"); driver.execute(txn -> { Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); for (IonValue ionValue : result) { // User code here to handle results } IOUsage ioUsage = result.getConsumedIOs(); long readIOs = ioUsage.getReadIOs(); });
.NET
using Amazon.IonDotnet.Builders; using Amazon.IonDotnet.Tree; using Amazon.QLDB.Driver; using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult; // This is one way of creating Ion values. We can also use a ValueFactory. // For more details, see: https://docs.aws.amazon.com/qldb/latest/developerguide/driver-cookbook-dotnet.html#cookbook-dotnet.ion IIonValue ionFirstName = IonLoader.Default.Load("Jim"); await driver.Execute(async txn => { IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); // Iterate through stream cursor to accumulate read IOs. await foreach (IIonValue ionValue in result) { // User code here to handle results. // Warning: It is bad practice to rely on results within a lambda block, unless // it is to check the state of a result. This is because lambdas are retryable. } var ioUsage = result.GetConsumedIOs(); var readIOs = ioUsage?.ReadIOs; });
Note

To convert to synchronous code, remove the await and async keywords, and change the IAsyncResult type to IResult.

Go
import ( "context" "fmt" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim") if err != nil { panic(err) } for result.Next(txn) { // User code here to handle results } ioUsage := result.GetConsumedIOs() readIOs := *ioUsage.GetReadIOs() fmt.Println(readIOs) return nil,nil })
Node.js
import { IOUsage, ResultReadable, TransactionExecutor } from "amazon-qldb-driver-nodejs"; await driver.executeLambda(async (txn: TransactionExecutor) => { const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim"); for await (const chunk of result) { // User code here to handle results } const ioUsage: IOUsage = result.getConsumedIOs(); const readIOs: number = ioUsage.getReadIOs(); });
Python
def get_read_ios(transaction_executor): cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim") for row in cursor: # User code here to handle results pass consumed_ios = cursor.get_consumed_ios() read_ios = consumed_ios.get('ReadIOs') qldb_driver.execute_lambda(lambda txn: get_read_ios(txn))

The following code examples show how to get read I/Os from the buffered cursor of a statement result. This returns the total read I/Os from ExecuteStatement and FetchPage requests.

Java
import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.qldb.IOUsage; import software.amazon.qldb.Result; IonSystem ionSystem = IonSystemBuilder.standard().build(); IonValue ionFirstName = ionSystem.newString("Jim"); Result result = driver.execute(txn -> { return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); }); IOUsage ioUsage = result.getConsumedIOs(); long readIOs = ioUsage.getReadIOs();
.NET
using Amazon.IonDotnet.Builders; using Amazon.IonDotnet.Tree; using Amazon.QLDB.Driver; using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult; IIonValue ionFirstName = IonLoader.Default.Load("Jim"); IAsyncResult result = await driver.Execute(async txn => { return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); }); var ioUsage = result.GetConsumedIOs(); var readIOs = ioUsage?.ReadIOs;
Note

To convert to synchronous code, remove the await and async keywords, and change the IAsyncResult type to IResult.

Go
import ( "context" "fmt" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim") if err != nil { return nil, err } return txn.BufferResult(result) }) if err != nil { panic(err) } qldbResult := result.(*qldbdriver.BufferedResult) ioUsage := qldbResult.GetConsumedIOs() readIOs := *ioUsage.GetReadIOs() fmt.Println(readIOs)
Node.js
import { IOUsage, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs"; const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => { return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim"); }); const ioUsage: IOUsage = result.getConsumedIOs(); const readIOs: number = ioUsage.getReadIOs();
Python
cursor = qldb_driver.execute_lambda( lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")) consumed_ios = cursor.get_consumed_ios() read_ios = consumed_ios.get('ReadIOs')
Note

The stream cursor is stateful because it paginates the result set. Therefore, the getConsumedIOs and getTimingInformation operations return the accumulated metrics from the time that you call them.

The buffered cursor buffers the result set in memory and returns the total accumulated metrics.

Timing information

The timing information metric describes the server-side processing time in milliseconds. Server-side processing time is defined as the amount of time that QLDB spends on processing a statement. This doesn't include time spent on network calls or pauses. This metric disambiguates the processing time on the QLDB service side from the processing time on the client side.

Using the QLDB console

To get a statement's timing information by using the QLDB console, do the following steps:

  1. Open the Amazon QLDB console at https://console.aws.amazon.com/qldb.

  2. In the navigation pane, choose PartiQL editor.

  3. Choose a ledger from the dropdown list of ledgers.

  4. In the query editor window, enter any statement of your choice, and then choose Run. The following is a query example.

    SELECT * FROM testTable WHERE firstName = 'Jim'

    To run a statement, you can also use the keyboard shortcut Ctrl+Enter for Windows, or Cmd+Return for macOS. For more keyboard shortcuts, see PartiQL editor keyboard shortcuts.

  5. Below the query editor window, your query results include server-side latency, which is the amount of time between when QLDB received the statement request, and when it sent the response. This is a subset of the total query duration.

You can also view the timing information of your query history by doing the following steps:

  1. In navigation pane, choose Recent queries under PartiQL editor.

  2. The Execution time (ms) column displays this timing information for each statement.

Using the QLDB driver

To get a statement's timing information by using the QLDB driver, call the getTimingInformation operation of the result's stream cursor or buffered cursor.

The following code examples show how to get the processing time from the stream cursor of a statement result.

Java
import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.qldb.Result; import software.amazon.qldb.TimingInformation; IonSystem ionSystem = IonSystemBuilder.standard().build(); IonValue ionFirstName = ionSystem.newString("Jim"); driver.execute(txn -> { Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); for (IonValue ionValue : result) { // User code here to handle results } TimingInformation timingInformation = result.getTimingInformation(); long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds(); });
.NET
using Amazon.IonDotnet.Builders; using Amazon.IonDotnet.Tree; using Amazon.QLDB.Driver; using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult; IIonValue ionFirstName = IonLoader.Default.Load("Jim"); await driver.Execute(async txn => { IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); // Iterate through stream cursor to accumulate processing time. await foreach(IIonValue ionValue in result) { // User code here to handle results. // Warning: It is bad practice to rely on results within a lambda block, unless // it is to check the state of a result. This is because lambdas are retryable. } var timingInformation = result.GetTimingInformation(); var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds; });
Note

To convert to synchronous code, remove the await and async keywords, and change the IAsyncResult type to IResult.

Go
import ( "context" "fmt" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim") if err != nil { panic(err) } for result.Next(txn) { // User code here to handle results } timingInformation := result.GetTimingInformation() processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds() fmt.Println(processingTimeMilliseconds) return nil, nil })
Node.js
import { ResultReadable, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs"; await driver.executeLambda(async (txn: TransactionExecutor) => { const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim"); for await (const chunk of result) { // User code here to handle results } const timingInformation: TimingInformation = result.getTimingInformation(); const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds(); });
Python
def get_processing_time_milliseconds(transaction_executor): cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim") for row in cursor: # User code here to handle results pass timing_information = cursor.get_timing_information() processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds') qldb_driver.execute_lambda(lambda txn: get_processing_time_milliseconds(txn))

The following code examples show how to get the processing time from the buffered cursor of a statement result. This returns the total processing time from ExecuteStatement and FetchPage requests.

Java
import com.amazon.ion.IonSystem; import com.amazon.ion.IonValue; import com.amazon.ion.system.IonSystemBuilder; import software.amazon.qldb.Result; import software.amazon.qldb.TimingInformation; IonSystem ionSystem = IonSystemBuilder.standard().build(); IonValue ionFirstName = ionSystem.newString("Jim"); Result result = driver.execute(txn -> { return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); }); TimingInformation timingInformation = result.getTimingInformation(); long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds();
.NET
using Amazon.IonDotnet.Builders; using Amazon.IonDotnet.Tree; using Amazon.QLDB.Driver; using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult; IIonValue ionFirstName = IonLoader.Default.Load("Jim"); IAsyncResult result = await driver.Execute(async txn => { return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName); }); var timingInformation = result.GetTimingInformation(); var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds;
Note

To convert to synchronous code, remove the await and async keywords, and change the IAsyncResult type to IResult.

Go
import ( "context" "fmt" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim") if err != nil { return nil, err } return txn.BufferResult(result) }) if err != nil { panic(err) } qldbResult := result.(*qldbdriver.BufferedResult) timingInformation := qldbResult.GetTimingInformation() processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds() fmt.Println(processingTimeMilliseconds)
Node.js
import { Result, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs"; const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => { return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim"); }); const timingInformation: TimingInformation = result.getTimingInformation(); const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds();
Python
cursor = qldb_driver.execute_lambda( lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")) timing_information = cursor.get_timing_information() processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds')
Note

The stream cursor is stateful because it paginates the result set. Therefore, the getConsumedIOs and getTimingInformation operations return the accumulated metrics from the time that you call them.

The buffered cursor buffers the result set in memory and returns the total accumulated metrics.

To learn how to query the system catalog, proceed to Querying the system catalog.