Spanner provides a set of built-in statistics tables to help you gain insight into your queries, reads, and transactions. To correlate statistics with your application code and to improve troubleshooting, you can add a tag (a free-form string) to Spanner read, query, and transaction operations in your application code. These tags are populated in statistics tables helping you to correlate and search based on tags.
Spanner supports two types of tags; request tags and transaction tags. As their names suggest, you can add transaction tags to transactions, and request tags to individual queries and reads APIs. You can set a transaction tag at the transaction scope and set individual request tags for each applicable API request within the transaction. Request tags and transaction tags that are set in the application code are populated in the columns of following statistics tables.
Statistics Table | Type of Tags populated in the statistics table |
---|---|
TopN Query Statistics | Request tags |
TopN Read Statistics | Request tags |
TopN Transaction Statistics | Transaction tags |
TopN Lock Statistics | Transaction tags |
Request tags
You can add an optional request tag to a query or a read request. Spanner
groups statistics by request tag, which is visible in the REQUEST_TAG
field of
both the
query statistics
and
read statistics
tables.
When to use request tags
The following are some of the scenarios that benefit from using request tags.
- Finding the source of a problematic query or read: Spanner collects statistics for reads and queries in built-in statistics tables. When you find the slow queries or high cpu consuming reads in the statistics table, if you have already assigned tags to those, then you can identify the source (application/microservice) that is calling these operations based on the information in the tag.
- Identifying reads or queries in statistics tables: Assigning request tags helps to filter rows in the statistics table based on the tags that you are interested in.
- Finding if queries from a particular application or microservice are slow: Request tags can help identify if queries from a particular application or microservice have higher latencies.
- Grouping statistics for a set of reads or queries: You can use request tags to track, compare, and report performance across a set of similar reads or queries. For example, if multiple queries are accessing a table/set of tables with the same access pattern, you can consider adding the same tag to all those queries to track them together.
How to assign request tags
The following sample shows how to set request tags using the Spanner client libraries.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
How to view request tags in statistics table
The following query returns the query statistics over 10 minute intervals.
SELECT t.text,
t.request_tag,
t.execution_count,
t.avg_latency_seconds,
t.avg_rows,
t.avg_bytes
FROM SPANNER_SYS.QUERY_STATS_TOP_10MINUTE AS t
LIMIT 3;
Let's take the following data as an example of the results we get back from our query.
text | request_tag | execution_count | avg_latency_seconds | avg_rows | avg_bytes |
---|---|---|---|---|---|
SELECT SingerId, AlbumId, AlbumTitle FROM Albums | app=concert,env=dev,action=select | 212 | 0.025 | 21 | 2365 |
select * from orders; | app=catalogsearch,env=dev,action=list | 55 | 0.02 | 16 | 33.35 |
SELECT SingerId, FirstName, LastName FROM Singers; | [empty string] | 154 | 0.048 | 42 | 486.33 |
From this table of results, we can see that if you have assigned a REQUEST_TAG
for a query, then it gets populated in the statistics table. If there is no
request tag assigned, it is displayed as an empty string.
For the tagged queries, the statistics are aggregated per tag (e.g. request tag
app=concert,env=dev,action=select
has an average latency of 0.025
seconds). If there is no tag assigned then the statistics are aggregated per
query (e.g. the query in the third row has an average latency of 0.048
seconds).
Transaction tags
An optional transaction tag can be added to individual transactions.
Spanner groups statistics by transaction tag, which is visible in the
TRANSACTION_TAG
field of
transaction statistics
tables.
When to use transaction tags
The following are some of the scenarios that benefit from using transaction tags.
- Finding the source of a problematic transaction: Spanner collects statistics for read-write transactions in the transaction statistics table. When you find slow transactions in the transaction statistics table, if you have already assigned tags to them, then you can identify the source (application/microservice) that is calling these transactions based on the information in the tag.
- Identifying transactions in statistics tables: Assigning transaction tags helps to filter rows in the transaction statistics table based on the tags that you are interested in. Without transaction tags, discovering what operations are represented by a statistic can be a cumbersome process. For example, for transaction statistics, you would have to examine the tables and columns involved in order to identify the untagged transaction.
- Finding if transactions from a particular application or microservice are slow: Transaction tags can help identify if transactions from a particular application or microservice have higher latencies.
- Grouping statistics for a set of transactions: You can use transaction tags to track, compare, and report performance for a set of similar transactions.
- Finding which transactions are accessing the columns involved in the lock conflict: Transaction tags can help pinpoint individual transactions causing lock conflicts in the Lock statistics tables.
- Streaming user change data out of Spanner using change streams: Change streams data records contain transaction tags for the transactions that modified the user data. This allows the reader of a change stream to associate changes with the transaction type based on tags.
How to assign transaction tags
The following sample shows how to set transaction tags using the Spanner client libraries. When you use a client library you can set a transaction tag at the beginning of the transaction call which gets applied to all the individual operations inside that transaction.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
How to view transaction tags in Transaction Statistics table
The following query returns the transaction statistics over 10 minute intervals.
SELECT t.fprint,
t.transaction_tag,
t.read_columns,
t.commit_attempt_count,
t.avg_total_latency_seconds
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE AS t
LIMIT 3;
Let's take the following data as an example of the results we get back from our query.
fprint | transaction_tag | read_columns | commit_attempt_count | avg_total_latency_seconds |
---|---|---|---|---|
40015598317 | app=concert,env=dev | [Venues._exists, Venues.VenueId, Venues.VenueName, Venues.Capacity] |
278802 | 0.3508 |
20524969030 | app=product,service=payment | [Singers.SingerInfo] | 129012 | 0.0142 |
77848338483 | [empty string] | [Singers.FirstName, Singers.LastName, Singers._exists] | 5357 | 0.048 |
From this table of results, we can see that if you have assigned a
TRANSACTION_TAG
to a transaction, then it gets populated in the transaction
statistics table. If there is no transaction tag assigned, it is displayed as an
empty string.
For the tagged transactions, the statistics are aggregated per transaction tag
(e.g. transaction tag app=concert,env=dev
a has an average
latency of 0.3508 seconds). If there is no tag assigned then the statistics
are aggregated per FPRINT
(e.g. 77848338483 in the third row has an
average latency of 0.048 seconds).
How to view transaction tags in Lock Statistics table
The following query returns the lock statistics over 10 minute intervals.
The CAST()
function converts the
row_range_start_key
BYTES field to a STRING.
SELECT
CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
s.lock_wait_seconds,
s.sample_lock_requests
FROM SPANNER_SYS.LOCK_STATS_TOP_10MINUTE s
LIMIT 2;
Let's take the following data as an example of the results we get back from our query.
row_range_start_key | lock_wait_seconds | sample_lock_requests |
---|---|---|
Songs(2,1,1) | 0.61 | LOCK_MODE: ReaderShared COLUMN: Singers.SingerInfo TRANSACTION_TAG: app=product,service=shipping LOCK_MODE: WriterShared COLUMN: Singers.SingerInfo TRANSACTION_TAG: app=product,service=payment |
albums(2,1+) | 0.48 | LOCK_MODE: ReaderShared COLUMN: users._exists1 TRANSACTION_TAG: [empty string] LOCK_MODE: WriterShared COLUMN: users._exists TRANSACTION_TAG: [empty string] |
From this table of results, we can see that if you have assigned a
TRANSACTION_TAG
to a transaction, then it gets populated in the lock
statistics table. If there is no transaction tag assigned, it is displayed
as an empty string.
Mapping between API methods and request/transaction tag
Request tags and transaction tags are applicable to specific API methods based on whether the transaction mode is a read-only transaction or a read-write transaction. Generally, transaction tags are applicable to read-write transactions whereas request tags are applicable to read-only transactions. The following table shows the mapping from API methods to applicable types of tags.
API Methods | Transaction Modes | Request Tag | Transaction Tag |
---|---|---|---|
Read, StreamingRead |
Read-only transaction | Yes | No |
Read-write transaction | Yes | Yes | |
ExecuteSql, ExecuteStreamingSql1 |
Read-only transaction1 | Yes1 | No |
Read-write transaction | Yes | Yes | |
ExecuteBatchDml | Read-write transaction | Yes | Yes |
BeginTransaction | Read-write transaction | No | Yes |
Commit | Read-write transaction | No | Yes |
1 For change stream queries executed using the Apache Beam SpannerIO
Dataflow connector, the REQUEST_TAG
contains a Dataflow job name.
Limitations
When adding tags to your reads, queries, and transactions, consider the following limitations:
- The length of a tag string is limited to 50 characters. Strings that exceed this limit are truncated.
- Only ASCII characters (32-126) are allowed in a tag. Arbitrary unicode characters are replaced by underscores.
- Any leading underscore (_) characters are removed from the string.
- Tags are case-sensitive. For example, if you add the request tag
APP=cart,ENV=dev
to one set of queries, and addapp=cart,env=dev
to another set of queries, Spanner aggregates statistics separately for each tag. Tags may be missing from the statistics tables under the following circumstance:
- If Spanner is unable to store statistics for all tagged operations run during the interval in tables, the system prioritizes operations with the highest consuming resources during the specified interval.
Tag naming
When assigning tags to your database operations, it is important to consider what information you want to convey in each tag string. The convention or pattern you choose makes your tags more effective. For example, proper tag naming makes it easier to correlate statistics with application code.
You can choose any tag you wish within the stated limitations. However, we recommend constructing a tag string as a set of key-value pairs separated by commas.
For example, assume that you are using a Spanner database for an
e-commerce use case. You might want to include information about the
application, development environment, and the action being taken by the query in
the request tag that you are going to assign to a particular query. You can
consider assigning the tag string in the key-value format as
app=cart,env=dev,action=update
.This means the query is called from the cart
application in the development environment, and is used to update the cart.
Suppose you have another query from a catalog search application and you assign
the tag string as app=catalogsearch,env=dev,action=list
. Now if any of these
queries show up in the query statistics table as high latency queries, you can
easily identify the source by using the tag.
Here are some examples of how a tagging pattern can be used to organize your operation statistics. These examples are not meant to be exhaustive; you can also combine them in your tag string using a delimiter such as a comma.
Tag keys | Examples of Tag-value pair | Description |
---|---|---|
Application | app=cart app=frontend app=catalogsearch |
Helps in identifying the application that is calling the operation. |
Environment | env=prod env=dev env=test env=staging |
Helps in identifying the environment that is associated with the operation. |
Framework | framework=spring framework=django framework=jetty |
Helps in identifying the framework that is associated with the operation. |
Action | action=list action=retrieve action=update |
Helps in identifying the action taken by the operation. |
Service | service=payment service=shipping |
Helps in identifying the microservice that is calling the operation. |
Things to Note
- When you assign a
REQUEST_TAG
, statistics for multiple queries that have the same tag string are grouped in a single row in query statistics table. Only the text of one of those queries is shown in theTEXT
field. - When you assign a
REQUEST_TAG
, statistics for multiple reads that have the same tag string are grouped in a single row in read statistics table. The set of all columns that are read are added to theREAD_COLUMNS
field. - When you assign a
TRANSACTION_TAG
, statistics for transactions that have the same tag string are grouped in a single row in transaction statistics table. The set of all columns that are written by the transactions are added to theWRITE_CONSTRUCTIVE_COLUMNS
field and the set of all columns that are read are added to theREAD_COLUMNS
field.
Troubleshooting scenarios using tags
Finding the source of a problematic transaction
The following query returns the raw data for the top transactions in the selected time period.
SELECT
fprint,
transaction_tag,
ROUND(avg_total_latency_seconds,4) as avg_total_latency_sec,
ROUND(avg_commit_latency_seconds,4) as avg_commit_latency_sec,
commit_attempt_count,
commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE interval_end = "2020-05-17T18:40:00"
ORDER BY avg_total_latency_seconds DESC;
The following table lists example data returned from our query, where we have three applications, namely cart, product and frontend, that own or query the same database.
Once you identify the transactions experiencing high latency, you can use the associated tags to identify the relevant part of your application code, and troubleshoot further using transaction statistics.
fprint | transaction_tag | avg_total_latency_sec | avg_commit_latency_sec | commit_attempt_count | commit_abort_count |
---|---|---|---|---|---|
7129109266372596045 | app=cart,service=order | 0.3508 | 0.0139 | 278802 | 142205 |
9353100217060788102 | app=cart,service=redis | 0.1633 | 0.0142 | 129012 | 27177 |
9353100217060788102 | app=product,service=payment | 0.1423 | 0.0133 | 5357 | 636 |
898069986622520747 | app=product,service=shipping | 0.0159 | 0.0118 | 4269 | 1 |
9521689070912159706 | app=frontend,service=ads | 0.0093 | 0.0045 | 164 | 0 |
11079878968512225881 | [empty string] | 0.031 | 0.015 | 14 | 0 |
Similarly, Request Tag can be used to find the source of a problematic query from query statistics table and source of problematic read from read statistics table.
Finding the latency and other stats for transactions from a particular application or microservice
If you have used the application name or microservice name in the tag string, it helps in filtering the transaction statistics table by tags that contain that application name or microservice name.
Suppose you have added new transactions to the payment app and you want to
look at latencies and other statistics of those new transactions. If you have
used the name of the payment application within the tag, you can filter the
transaction statistics table for only those tags that contain app=payment
.
The following query returns the transaction statistics for payment app over 10 minute intervals.
SELECT
transaction_tag,
avg_total_latency_sec,
avg_commit_latency_sec,
commit_attempt_count,
commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE STARTS_WITH(transaction_tag, "app=payment")
LIMIT 3;
Here's some example output:
transaction_tag | avg_total_latency_sec | avg_commit_latency_sec | commit_attempt_count | commit_abort_count |
---|---|---|---|---|
app=payment,action=update | 0.3508 | 0.0139 | 278802 | 142205 |
app=payment,action=transfer | 0.1633 | 0.0142 | 129012 | 27177 |
app=payment, action=retrieve | 0.1423 | 0.0133 | 5357 | 636 |
Similarly, you can find queries or reads from a specific application in query statistics or read statistics table using request tags.
Discovering the transactions involved in lock conflict
To find out which transactions and row keys experienced the high lock wait times,
we query the LOCK_STAT_TOP_10MINUTE
table, which lists the row keys, columns,
and corresponding transactions that are involved in the lock conflict.
SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
t.total_lock_wait_seconds,
s.lock_wait_seconds,
s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
t.interval_end = "2020-05-17T18:40:00" and s.interval_end = t.interval_end;
Here's some example output from our query:
row_range_start_key | total_lock_wait_seconds | lock_wait_seconds | frac_of_total | sample_lock_requests |
---|---|---|---|---|
Singers(32) | 2.37 | 1.76 | 1 | LOCK_MODE: WriterShared COLUMN: Singers.SingerInfo TRANSACTION_TAG: app=cart,service=order LOCK_MODE: ReaderShared COLUMN: Singers.SingerInfo TRANSACTION_TAG: app=cart,service=redis |
From this table of results, we can see the conflict happened on the Singers
table at key SingerId=32. The Singers.SingerInfo
is the column where the
lock conflict happened between ReaderShared
and WriterShared
. You can also
identify corresponding transactions (app=cart,service=order
and
app=cart,service=redis
) that are experiencing the conflict.
Once the transactions causing the lock conflicts are identified, you can now focus on these transactions by using Transaction Statistics to get a better sense of what the transactions are doing and if you can avoid a conflict or reduce the time for which the locks are held. For more information, see Best practices to reduce lock contention.
What's next
- Learn about other Introspection tools.
- Learn about other information Spanner stores for each database in the database's information schema tables.
- Learn more about SQL best practices for Spanner.
- Learn more about Investigating high CPU utilization.