Repository: cockroachdb/docs
Pull request #19569: DOC-11498 Docs for obs: Support workload-level index recommendations and insights
Author: login: florence-crl
Created: 2025-04-28T20:41:40Z | Merged: 2025-05-13T20:45:18Z

Fixes DOC-11498

In insights.md include file, (1) added workload_index_recs function section and (2) updated the img tags to use files in v25.2 directory.

Rendered previews:

- [`workload_index_recs` function](https://deploy-preview-19569--cockroachdb-docs.netlify.app/docs/v25.2/ui-insights-page#workload_index_recs-function)


Diff:
diff --git a/src/current/_includes/v25.2/ui/insights.md b/src/current/_includes/v25.2/ui/insights.md
index 3ad9e4902e5..8c1c9b43858 100644
--- a/src/current/_includes/v25.2/ui/insights.md
+++ b/src/current/_includes/v25.2/ui/insights.md
@@ -185,11 +185,11 @@ The Workload Insights tab surfaces the following type of insights:
 
 The transaction or statement execution failed. The following screenshot shows a failed transaction execution:
 
-<img src="{{ 'images/v24.2/transaction_executions_failed.png' | relative_url }}" alt="Failed transaction execution" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/transaction_executions_failed.png' | relative_url }}" alt="Failed transaction execution" style="border:1px solid #eee;max-width:100%" />
 
 The following screenshot shows the default details of the preceding failed transaction execution.
 
-<img src="{{ 'images/v24.2/transaction_execution_details_failed.png' | relative_url }}" alt="Failed transaction execution details" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/transaction_execution_details_failed.png' | relative_url }}" alt="Failed transaction execution details" style="border:1px solid #eee;max-width:100%" />
 
 The **Insights** column shows the name of the insight, in this case **Failed Execution**. The **Details** column provides the **Error Code** and **Error Message**. CockroachDB uses [PostgreSQL Error Codes](https://www.postgresql.org/docs/current/errcodes-appendix.html). In this example, Error Code `40001` is a `serialization_failure`.
 
@@ -197,7 +197,7 @@ The **Insights** column shows the name of the insight, in this case **Failed Exe
 
 The following screenshot shows the conditional details of the preceding failed transaction execution. In this case, there was a *serialization conflict*, also known as an *isolation conflict*, due to [transaction contention]({{ link_prefix }}performance-recipes.html#transaction-contention). (For transaction contention that causes *lock contention*, see [High Contention](#high-contention)).
 
-<img src="{{ 'images/v24.2/transaction_execution_details_failed_conditional.png' | relative_url }}" alt="Failed transaction execution details conditional" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/transaction_execution_details_failed_conditional.png' | relative_url }}" alt="Failed transaction execution details conditional" style="border:1px solid #eee;max-width:100%" />
 
 To capture more information in the event of a failed transaction execution due to a serialization conflict, set the [`sql.contention.record_serialization_conflicts.enabled`]({{ link_prefix }}cluster-settings.html#setting-sql-contention-record-serialization-conflicts-enabled) cluster setting to `true` (default). With this setting enabled, when the **Error Code** is `40001` and the **Error Message** specifically has [`RETRY_SERIALIZABLE - failed preemptive refresh`]({{ link_prefix }}transaction-retry-error-reference.html#failed_preemptive_refresh)` due to conflicting locks`, a conditional **Failed Execution** section is displayed with **Conflicting Transaction** and **Conflicting Location** information.
 
@@ -211,11 +211,11 @@ To troubleshoot, refer to the performance tuning recipe for [identifying and unb
 
 The following screenshot shows the execution of a transaction flagged with **High Contention**:
 
-<img src="{{ 'images/v24.2/transaction_execution.png' | relative_url }}" alt="Transaction execution" style="border:1px solid #eeqe;max-width:100%" />
+<img src="{{ 'images/v25.2/transaction_execution.png' | relative_url }}" alt="Transaction execution" style="border:1px solid #eeqe;max-width:100%" />
 
 The following screenshot shows the execution details of the preceding transaction execution:
 
-<img src="{{ 'images/v24.2/transaction_execution_details.png' | relative_url }}" alt="Transaction execution details" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/transaction_execution_details.png' | relative_url }}" alt="Transaction execution details" style="border:1px solid #eee;max-width:100%" />
 
 ### High Retry Count
 
@@ -236,11 +236,11 @@ The statement execution has resulted in one or more [index recommendations](#sch
 
 The following screenshot shows the statement execution of the query described in [Use the right index]({{ link_prefix }}apply-statement-performance-rules.html#rule-2-use-the-right-index):
 
-<img src="{{ 'images/v24.2/statement_executions.png' | relative_url }}" alt="Statement execution" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/statement_executions.png' | relative_url }}" alt="Statement execution" style="border:1px solid #eee;max-width:100%" />
 
 The following screenshot shows the execution details of the preceding statement execution:
 
-<img src="{{ 'images/v24.2/statement_execution_details.png' | relative_url }}" alt="Statement execution details" style="border:1px solid #eee;max-width:100%" />
+<img src="{{ 'images/v25.2/statement_execution_details.png' | relative_url }}" alt="Statement execution details" style="border:1px solid #eee;max-width:100%" />
 
 The **Insights** column shows the name of the insight, in this case **Suboptimal Plan**. The **Details** column provides details on the insight, such as a **Description** with the cause of the suboptimal plan and a **Recommendation** with a `CREATE INDEX` statement. The final column contains a **Create Index** button. Click the **Create Index** button to execute the recommended statement to mitigate the cause of the insight.
 
@@ -258,11 +258,13 @@ This view lists the [indexes]({{ link_prefix }}indexes.html) that have not been
 - The drop recommendations are the same as those on the [**Databases**]({{ link_prefix }}ui-databases-page.html#index-recommendations) page.
 - The create, alter, and replace recommendations are the same as those on the [Explain Plans tab]({{ link_prefix }}ui-statements-page.html#insights) on the Statements page. Whereas the **Explain Plans** tab shows all recommendations, the **Schema Insights** view shows only the latest recommendations for that statement fingerprint. If you execute a statement again after creating or updating an index, the recommendation disappears.
 
-The following screenshot shows the insight that displays after you run the query described in [Use the right index]({{ link_prefix }}apply-statement-performance-rules.html#rule-2-use-the-right-index) 6 or more times:
+<a id="schema-insights-example"></a>
 
-<img src="{{ 'images/v24.2/schema_insight.png' | relative_url }}" alt="Schema insight" style="border:1px solid #eee;max-width:100%" />
+The following screenshot shows the insight that displays after you run the query described in [Use the right index]({{ link_prefix }}apply-statement-performance-rules.html#rule-2-use-the-right-index) six or more times:
 
-CockroachDB uses the threshold of 6 executions before offering an insight because it assumes that you are no longer merely experimenting with a query at that point.
+<img src="{{ 'images/v25.2/schema_insight.png' | relative_url }}" alt="Schema insight" style="border:1px solid #eee;max-width:100%" />
+
+CockroachDB uses the threshold of six executions before offering an insight because it assumes that you are no longer merely experimenting with a query at that point.
 
 - **Insights:** Contains one of the following insight types: **Create Index**, **Alter Index**, **Replace Index**, **Drop Unused Index**.
 - **Details:** Details for each insight. Different insight types display different details fields:
@@ -272,6 +274,78 @@ CockroachDB uses the threshold of 6 executions before offering an insight becaus
 
 [Admin users]({{ link_prefix }}security-reference/authorization.html#admin-role) will see an action button in the final column, which will execute the SQL statement suggested by the schema insight, for example "Create Index". Upon clicking the action button, a confirmation dialog displays a warning about the cost of [online schema changes]({{ link_prefix }}online-schema-changes.html) and the option to copy the SQL statement for later execution in a SQL client.
 
+### `workload_index_recs` function
+
+{{site.data.alerts.callout_info}}
+{% include_cached feature-phases/preview.md %}
+{{site.data.alerts.end}}
+
+{% include_cached new-in.html version="v25.2" %} The SQL built-in function [`workload_index_recs`]({{ link_prefix }}functions-and-operators.html#workload_index_recs) returns index recommendations and the fingerprint IDs of the statements they impact. The function returns two columns:
+
+- `index_rec` (`STRING`): Contains the index recommendation.
+- `fingerprint_ids` (`BYTES[]`): Contains the fingerprint IDs of the affected statements.
+
+You can use the `workload_index_recs` function to determine workload-level index recommendations.
+
+By default, the function returns index recommendations sourced from all statement fingerprints in the [`crdb_internal.statement_statistics`]({{ link_prefix }}crdb-internal.html#statement_statistics) table. When passed an optional [`TIMESTAMPTZ`]({{ link_prefix }}timestamp.html) parameter, the function will provide workload-level index recommendations only for statements executed after the timestamp. For example, `SELECT workload_index_recs('2025-05-08 16:00:00+00');` returns index recommendations for statements executed after `'2025-05-08 16:00:00+00'`.
+
+#### `workload_index_recs` example
+
+After running the [query]({{ link_prefix }}apply-statement-performance-rules.html#rule-2-use-the-right-index) mentioned in the preceding [**Schema Insights** tab](#schema-insights-tab) section, run the following related query more than six times to generate another **Create Index** insight.
+
+{% include_cached copy-clipboard.html %}
+~~~ sql
+SELECT
+  name, users.city, rides.city, count(rides.id) AS sum
+FROM
+  users JOIN rides ON users.id = rides.rider_id
+WHERE
+  rides.start_time BETWEEN '2018-12-31 00:00:00' AND '2020-01-01 00:00:00'
+GROUP BY
+  name, users.city, rides.city
+ORDER BY
+  sum DESC
+LIMIT
+  10;
+~~~
+
+Run the `workload_index_recs` function to return the `CREATE INDEX` recommendation associated with two fingerprint IDs:
+
+{% include_cached copy-clipboard.html %}
+~~~ sql
+SELECT workload_index_recs();
+~~~
+
+~~~
+                                                       workload_index_recs
+----------------------------------------------------------------------------------------------------------------------------------
+  ("CREATE INDEX ON movr.public.rides (start_time) STORING (rider_id);","{""\\\\x95a325e25bdbdc06"",""\\\\x4784cb829aab2542""}")
+~~~
+
+To display the query strings corresponding to the fingerprint IDs, run a query that joins the `workload_index_recs` function with the [`crdb_internal.statement_statistics`]({{ link_prefix }}crdb-internal.html#statement_statistics) table.
+
+{% include_cached copy-clipboard.html %}
+~~~ sql
+SELECT ss.index_recommendations,
+(ss.statistics->'statistics'->>'lastExecAt')::TIMESTAMPTZ AS lastExecAt, -- Time the statement was last execut
+ss.fingerprint_id,
+ss.app_name,
+ss.metadata->>'query' AS query
+FROM crdb_internal.statement_statistics AS ss -- Return data from the statement_statistics table
+JOIN (
+    SELECT unnest((rec).fingerprint_ids) AS fingerprint_id -- Return each fingerprint ID from the array as a row
+    FROM workload_index_recs() AS rec
+) AS fids ON ss.fingerprint_id = fids.fingerprint_id
+ORDER BY ss.index_recommendations, lastExecAt;
+~~~
+
+~~~
+                                index_recommendations                               |          lastexecat           |   fingerprint_id   |     app_name     |                                                                                                         query
+------------------------------------------------------------------------------------+-------------------------------+--------------------+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  {"creation : CREATE INDEX ON movr.public.rides (start_time) STORING (rider_id);"} | 2025-05-08 15:59:21.969934+00 | \x4784cb829aab2542 | $ cockroach demo | SELECT name, count(rides.id) AS sum FROM users JOIN rides ON users.id = rides.rider_id WHERE rides.start_time BETWEEN _ AND _ GROUP BY name ORDER BY sum DESC LIMIT _
+  {"creation : CREATE INDEX ON movr.public.rides (start_time) STORING (rider_id);"} | 2025-05-08 16:10:59.479173+00 | \x95a325e25bdbdc06 | $ cockroach demo | SELECT name, users.city, rides.city, count(rides.id) AS sum FROM users JOIN rides ON users.id = rides.rider_id WHERE rides.start_time BETWEEN _ AND _ GROUP BY name, users.city, rides.city ORDER BY sum DESC LIMIT _
+~~~
+
 ## Search and filter
 
 By default, the Workload Insights view shows all statements or transactions that have insights. By default, the Schema Insights view shows all Schema Insights.
diff --git a/src/current/v25.2/cockroachdb-feature-availability.md b/src/current/v25.2/cockroachdb-feature-availability.md
index 9f3a1235e46..1bbf9cc178c 100644
--- a/src/current/v25.2/cockroachdb-feature-availability.md
+++ b/src/current/v25.2/cockroachdb-feature-availability.md
@@ -55,6 +55,10 @@ A [vector index]({% link {{ page.version.version }}/vector-indexes.md %}) enable
 
 [JSONPath]({% link {{ page.version.version }}/jsonpath.md %}) expressions and functions can be used to query and filter [`JSONB`]({% link {{ page.version.version }}/jsonb.md %}) data, using [JSONPath expressions]({% link {{ page.version.version }}/jsonpath.md %}#jsonpath-expression) as arguments in [JSONPath functions]({% link {{ page.version.version }}/jsonpath.md %}#jsonpath-functions).
 
+### Workload-level index recommendations
+
+The SQL built-in function [workload_index_recs]({% link {{ page.version.version }}/ui-insights-page.md %}#workload_index_recs-function) returns index recommendations and the fingerprint IDs of the statements they impact.
+
 ### Triggers
 
 [Triggers]({% link {{ page.version.version }}/triggers.md %}) are in Preview. A trigger executes a function when one or more specified SQL operations is performed on a table. Triggers respond to data changes by adding logic within the database, rather than in an application. They can be used to modify data before it is inserted, maintain data consistency across rows or tables, or record an update to a row.


Reviews:
user: login: kevin-v-ngo
body: Hi @florence-crl, looks great and love the example joining with statement_statistics so that users can view the query text. 

Can we add a description on the time window of the workload over which we look for index recommendations? The default behavior including allowing users to flexibly specifying a timestamp.

@kyle-a-wong, can you confirm the default behavior? If i recall, we take all the statements in statement_statistics. And if a timestamp is specified we filter and only consider those the statements after the timestamp.
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-06T16:04:55Z
user: login: kevin-v-ngo
body: Approved pending small change.
state: APPROVED
author_association: NONE
submitted_at: 2025-05-13T13:37:24Z
user: login: florence-crl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T16:57:07Z
user: login: katmayb
body: LGTM!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T19:56:32Z

Review comments:
diff_hunk: @@ -272,6 +274,76 @@ CockroachDB uses the threshold of 6 executions before offering an insight becaus
 
 [Admin users]({{ link_prefix }}security-reference/authorization.html#admin-role) will see an action button in the final column, which will execute the SQL statement suggested by the schema insight, for example "Create Index". Upon clicking the action button, a confirmation dialog displays a warning about the cost of [online schema changes]({{ link_prefix }}online-schema-changes.html) and the option to copy the SQL statement for later execution in a SQL client.
 
+### `workload_index_recs` function
+
+{{site.data.alerts.callout_info}}
+{% include_cached feature-phases/preview.md %}
+{{site.data.alerts.end}}
+
+The SQL built-in function [`workload_index_recs`]({% link {{ page.version.version }}/functions-and-operators.md %}#workload_index_recs) returns index recommendations and the fingerprint IDs of the statements they impact. The function returns two columns:
+
+- `index_rec` (`STRING`): Contains the index recommendation.
+- `fingerprint_ids` (`BYTES[]`): Contains the fingerprint IDs of the affected statements.
+
+You can use the `workload_index_recs` function to determine workload-level index recommendations.
+
+By default, the function returns index recommendations sourced from all statement fingerprints in the [`crdb_internal.statement_statistics`]({% link {{ page.version.version }}/crdb-internal.md %}#statement_statistics) table. When passed an optional [`TIMESTAMPTZ`]({% link {{ page.version.version }}/timestamp.md %}) parameter, such as `SELECT workload_index_recs('2025-05-08 16:00:00+00')`, the function will provide index recommendations only for statements executed after `'2025-05-08 16:00:00+00'`.
path: src/current/_includes/v25.2/ui/insights.md
user: login: kevin-v-ngo
body: Nit: Can we describe what the timestamp parameter does (return workload-level index recommendations for statements after the timestamp) and then jump into the example? 
created_at: 2025-05-13T13:37:04Z
author_association: NONE
diff_hunk: @@ -272,6 +274,76 @@ CockroachDB uses the threshold of 6 executions before offering an insight becaus
 
 [Admin users]({{ link_prefix }}security-reference/authorization.html#admin-role) will see an action button in the final column, which will execute the SQL statement suggested by the schema insight, for example "Create Index". Upon clicking the action button, a confirmation dialog displays a warning about the cost of [online schema changes]({{ link_prefix }}online-schema-changes.html) and the option to copy the SQL statement for later execution in a SQL client.
 
+### `workload_index_recs` function
+
+{{site.data.alerts.callout_info}}
+{% include_cached feature-phases/preview.md %}
+{{site.data.alerts.end}}
+
+The SQL built-in function [`workload_index_recs`]({% link {{ page.version.version }}/functions-and-operators.md %}#workload_index_recs) returns index recommendations and the fingerprint IDs of the statements they impact. The function returns two columns:
+
+- `index_rec` (`STRING`): Contains the index recommendation.
+- `fingerprint_ids` (`BYTES[]`): Contains the fingerprint IDs of the affected statements.
+
+You can use the `workload_index_recs` function to determine workload-level index recommendations.
+
+By default, the function returns index recommendations sourced from all statement fingerprints in the [`crdb_internal.statement_statistics`]({% link {{ page.version.version }}/crdb-internal.md %}#statement_statistics) table. When passed an optional [`TIMESTAMPTZ`]({% link {{ page.version.version }}/timestamp.md %}) parameter, such as `SELECT workload_index_recs('2025-05-08 16:00:00+00')`, the function will provide index recommendations only for statements executed after `'2025-05-08 16:00:00+00'`.
path: src/current/_includes/v25.2/ui/insights.md
user: login: florence-crl
body: updated
created_at: 2025-05-13T16:57:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-28T20:42:03Z
body: <h3>Files changed:</h3><ul><li>src/current/_includes/v25.2/ui/insights.md:</li><ul><li><a href="https://deploy-preview-19569--cockroachdb-docs.netlify.app/docs/v25.2/ui-insights-page.html" target="_blank" rel="noopener">src/current/v25.2/ui-insights-page.md</a></li></ul><li><a href="https://deploy-preview-19569--cockroachdb-docs.netlify.app/docs/v25.2/cockroachdb-feature-availability.html" target="_blank" rel="noopener">src/current/v25.2/cockroachdb-feature-availability.md</a></li></ul>
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-28T20:42:17Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *cockroachdb-interactivetutorials-docs* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 8658d11846ccfcc19b56f1f1f7804c6fd08cbf0f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/cockroachdb-interactivetutorials-docs/deploys/6823ab5be39d730008f350c7 |
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-28T20:42:20Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *cockroachdb-api-docs* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 8658d11846ccfcc19b56f1f1f7804c6fd08cbf0f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/cockroachdb-api-docs/deploys/6823ab5b0046b000074fb5e3 |
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-28T20:51:21Z
body: ### <span aria-hidden="true">✅</span> Netlify Preview


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 8658d11846ccfcc19b56f1f1f7804c6fd08cbf0f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/cockroachdb-docs/deploys/6823ab5ba026e900086930aa |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-19569--cockroachdb-docs.netlify.app](https://deploy-preview-19569--cockroachdb-docs.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTE5NTY5LS1jb2Nrcm9hY2hkYi1kb2NzLm5ldGxpZnkuYXBwIn0.iaTbBeLco-G5qkqQfZbMKiTpDEmi5_O9pZvnqTB-rKI)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [cockroachdb-docs Preview](https://deploy-preview-19569--cockroachdb-docs.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/cockroachdb-docs/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: kyle-a-wong
created_at: 2025-05-06T16:21:32Z
body: > Hi @florence-crl, looks great and love the example joining with statement_statistics so that users can view the query text.
> 
> Can we add a description on the time window of the workload over which we look for index recommendations? The default behavior including allowing users to flexibly specifying a timestamp.
> 
> @kyle-a-wong, can you confirm the default behavior? If i recall, we take all the statements in statement_statistics. And if a timestamp is specified we filter and only consider those the statements after the timestamp.

@kevin-v-ngo yes, the default behavior will return index_recommendations sourced from all statements in statement_statistics. Providing a time stamp, such as `select workload_index_rec('2025-05-5')` will only provide workload index recs that come after `2025-05-5 00:00:00`
author_association: CONTRIBUTOR
user: login: florence-crl
created_at: 2025-05-08T20:54:42Z
body: @kevin-v-ngo and @kyle-a-wong I added TIMESTAMPTZ parameter info. Please review again. thanks!
author_association: CONTRIBUTOR
user: login: florence-crl
created_at: 2025-05-13T20:28:36Z
body: TFTRs!
author_association: CONTRIBUTOR