> ## Documentation Index
> Fetch the complete documentation index at: https://planetscale.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Query Insights

> Analyze query latency, resource use, and cross-shard execution in Neki.

export const PlatformAvailability = ({current, vitess, postgres, neki}) => {
  const docsHref = path => {
    if (!path) return path;
    const normalized = path.startsWith('/') ? path : `/${path}`;
    return normalized;
  };
  const labels = {
    vitess: 'Vitess',
    postgres: 'Postgres',
    neki: 'Neki'
  };
  const combinedLabels = {
    both: 'Vitess and Postgres',
    all: 'Vitess, Neki, and Postgres',
    'postgres-neki': 'Postgres and Neki'
  };
  if (combinedLabels[current]) {
    return <div className="not-prose mb-5 flex flex-wrap items-center gap-2" role="group" aria-label="Platform availability">
        <span data-engine="both" data-state="current" aria-current="true" className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-full border px-2.5 py-1 text-[13px] font-semibold leading-tight no-underline data-[engine=vitess]:data-[state=current]:border-[#ffc59b] data-[engine=vitess]:data-[state=current]:bg-[#ffe8d8] data-[engine=vitess]:data-[state=current]:text-[#672002] dark:data-[engine=vitess]:data-[state=current]:border-[#962d00] dark:data-[engine=vitess]:data-[state=current]:bg-[#3c1403] dark:data-[engine=vitess]:data-[state=current]:text-[#ffe8d8] data-[engine=vitess]:data-[state=link]:border-[#ffc59b] data-[engine=vitess]:data-[state=link]:bg-transparent data-[engine=vitess]:data-[state=link]:text-[#b83a05] dark:data-[engine=vitess]:data-[state=link]:border-[#962d00] dark:data-[engine=vitess]:data-[state=link]:bg-transparent dark:data-[engine=vitess]:data-[state=link]:text-[#ffc59b] data-[engine=postgres]:data-[state=current]:border-[#a9dffe] data-[engine=postgres]:data-[state=current]:bg-[#ddf2ff] data-[engine=postgres]:data-[state=current]:text-[#0e3682] dark:data-[engine=postgres]:data-[state=current]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=current]:bg-[#08204e] dark:data-[engine=postgres]:data-[state=current]:text-[#ddf2ff] data-[engine=postgres]:data-[state=link]:border-[#a9dffe] data-[engine=postgres]:data-[state=link]:bg-transparent data-[engine=postgres]:data-[state=link]:text-[#0b6ec5] dark:data-[engine=postgres]:data-[state=link]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=link]:bg-transparent dark:data-[engine=postgres]:data-[state=link]:text-[#73c7f9] data-[engine=neki]:data-[state=current]:border-[#fbca00] data-[engine=neki]:data-[state=current]:bg-[#fbca00] data-[engine=neki]:data-[state=current]:text-[#1a1a1a] dark:data-[engine=neki]:data-[state=current]:border-[#fbca00] dark:data-[engine=neki]:data-[state=current]:bg-[#fbca00] dark:data-[engine=neki]:data-[state=current]:text-[#1a1a1a] data-[engine=neki]:data-[state=link]:border-[#fbca00] data-[engine=neki]:data-[state=link]:bg-transparent data-[engine=neki]:data-[state=link]:text-[#8f7200] dark:data-[engine=neki]:data-[state=link]:border-[#fbca00] dark:data-[engine=neki]:data-[state=link]:bg-transparent dark:data-[engine=neki]:data-[state=link]:text-[#fbca00] data-[engine=both]:data-[state=current]:border-[#d4d4d4] data-[engine=both]:data-[state=current]:bg-[#f0f0f0] data-[engine=both]:data-[state=current]:text-[#3d3d3d] dark:data-[engine=both]:data-[state=current]:border-[#525252] dark:data-[engine=both]:data-[state=current]:bg-[#2a2a2a] dark:data-[engine=both]:data-[state=current]:text-[#e5e5e5]">
          {combinedLabels[current]}
        </span>
      </div>;
  }
  const hasVitess = current === 'vitess' || Boolean(vitess);
  const hasPostgres = current === 'postgres' || Boolean(postgres);
  const hasNeki = current === 'neki' || Boolean(neki);
  const only = [hasVitess, hasPostgres, hasNeki].filter(Boolean).length === 1;
  const engines = [];
  if (current === 'vitess' || current === 'postgres' || current === 'neki') engines.push(current);
  if (hasVitess && current !== 'vitess') engines.push('vitess');
  if (hasNeki && current !== 'neki') engines.push('neki');
  if (hasPostgres && current !== 'postgres') engines.push('postgres');
  return <div className="not-prose mb-5 flex flex-wrap items-center gap-2" role="group" aria-label="Platform availability">
      {engines.map(engine => {
    const isCurrent = current === engine;
    const href = docsHref(engine === 'vitess' ? vitess : engine === 'postgres' ? postgres : neki);
    const label = only ? `${labels[engine]} only` : labels[engine];
    const state = isCurrent || !href ? 'current' : 'link';
    if (isCurrent || !href) {
      return <span key={engine} data-engine={engine} data-state={state} aria-current={isCurrent ? 'true' : undefined} className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-full border px-2.5 py-1 text-[13px] font-semibold leading-tight no-underline data-[engine=vitess]:data-[state=current]:border-[#ffc59b] data-[engine=vitess]:data-[state=current]:bg-[#ffe8d8] data-[engine=vitess]:data-[state=current]:text-[#672002] dark:data-[engine=vitess]:data-[state=current]:border-[#962d00] dark:data-[engine=vitess]:data-[state=current]:bg-[#3c1403] dark:data-[engine=vitess]:data-[state=current]:text-[#ffe8d8] data-[engine=vitess]:data-[state=link]:border-[#ffc59b] data-[engine=vitess]:data-[state=link]:bg-transparent data-[engine=vitess]:data-[state=link]:text-[#b83a05] dark:data-[engine=vitess]:data-[state=link]:border-[#962d00] dark:data-[engine=vitess]:data-[state=link]:bg-transparent dark:data-[engine=vitess]:data-[state=link]:text-[#ffc59b] data-[engine=postgres]:data-[state=current]:border-[#a9dffe] data-[engine=postgres]:data-[state=current]:bg-[#ddf2ff] data-[engine=postgres]:data-[state=current]:text-[#0e3682] dark:data-[engine=postgres]:data-[state=current]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=current]:bg-[#08204e] dark:data-[engine=postgres]:data-[state=current]:text-[#ddf2ff] data-[engine=postgres]:data-[state=link]:border-[#a9dffe] data-[engine=postgres]:data-[state=link]:bg-transparent data-[engine=postgres]:data-[state=link]:text-[#0b6ec5] dark:data-[engine=postgres]:data-[state=link]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=link]:bg-transparent dark:data-[engine=postgres]:data-[state=link]:text-[#73c7f9] data-[engine=neki]:data-[state=current]:border-[#fbca00] data-[engine=neki]:data-[state=current]:bg-[#fbca00] data-[engine=neki]:data-[state=current]:text-[#1a1a1a] dark:data-[engine=neki]:data-[state=current]:border-[#fbca00] dark:data-[engine=neki]:data-[state=current]:bg-[#fbca00] dark:data-[engine=neki]:data-[state=current]:text-[#1a1a1a] data-[engine=neki]:data-[state=link]:border-[#fbca00] data-[engine=neki]:data-[state=link]:bg-transparent data-[engine=neki]:data-[state=link]:text-[#8f7200] dark:data-[engine=neki]:data-[state=link]:border-[#fbca00] dark:data-[engine=neki]:data-[state=link]:bg-transparent dark:data-[engine=neki]:data-[state=link]:text-[#fbca00] data-[engine=both]:data-[state=current]:border-[#d4d4d4] data-[engine=both]:data-[state=current]:bg-[#f0f0f0] data-[engine=both]:data-[state=current]:text-[#3d3d3d] dark:data-[engine=both]:data-[state=current]:border-[#525252] dark:data-[engine=both]:data-[state=current]:bg-[#2a2a2a] dark:data-[engine=both]:data-[state=current]:text-[#e5e5e5]">
              {label}
            </span>;
    }
    return <a key={engine} href={href} data-engine={engine} data-state={state} title={`View ${labels[engine]} documentation`} className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-full border px-2.5 py-1 text-[13px] font-semibold leading-tight no-underline data-[engine=vitess]:data-[state=current]:border-[#ffc59b] data-[engine=vitess]:data-[state=current]:bg-[#ffe8d8] data-[engine=vitess]:data-[state=current]:text-[#672002] dark:data-[engine=vitess]:data-[state=current]:border-[#962d00] dark:data-[engine=vitess]:data-[state=current]:bg-[#3c1403] dark:data-[engine=vitess]:data-[state=current]:text-[#ffe8d8] data-[engine=vitess]:data-[state=link]:border-[#ffc59b] data-[engine=vitess]:data-[state=link]:bg-transparent data-[engine=vitess]:data-[state=link]:text-[#b83a05] dark:data-[engine=vitess]:data-[state=link]:border-[#962d00] dark:data-[engine=vitess]:data-[state=link]:bg-transparent dark:data-[engine=vitess]:data-[state=link]:text-[#ffc59b] data-[engine=postgres]:data-[state=current]:border-[#a9dffe] data-[engine=postgres]:data-[state=current]:bg-[#ddf2ff] data-[engine=postgres]:data-[state=current]:text-[#0e3682] dark:data-[engine=postgres]:data-[state=current]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=current]:bg-[#08204e] dark:data-[engine=postgres]:data-[state=current]:text-[#ddf2ff] data-[engine=postgres]:data-[state=link]:border-[#a9dffe] data-[engine=postgres]:data-[state=link]:bg-transparent data-[engine=postgres]:data-[state=link]:text-[#0b6ec5] dark:data-[engine=postgres]:data-[state=link]:border-[#144eb6] dark:data-[engine=postgres]:data-[state=link]:bg-transparent dark:data-[engine=postgres]:data-[state=link]:text-[#73c7f9] data-[engine=neki]:data-[state=current]:border-[#fbca00] data-[engine=neki]:data-[state=current]:bg-[#fbca00] data-[engine=neki]:data-[state=current]:text-[#1a1a1a] dark:data-[engine=neki]:data-[state=current]:border-[#fbca00] dark:data-[engine=neki]:data-[state=current]:bg-[#fbca00] dark:data-[engine=neki]:data-[state=current]:text-[#1a1a1a] data-[engine=neki]:data-[state=link]:border-[#fbca00] data-[engine=neki]:data-[state=link]:bg-transparent data-[engine=neki]:data-[state=link]:text-[#8f7200] dark:data-[engine=neki]:data-[state=link]:border-[#fbca00] dark:data-[engine=neki]:data-[state=link]:bg-transparent dark:data-[engine=neki]:data-[state=link]:text-[#fbca00] data-[engine=both]:data-[state=current]:border-[#d4d4d4] data-[engine=both]:data-[state=current]:bg-[#f0f0f0] data-[engine=both]:data-[state=current]:text-[#3d3d3d] dark:data-[engine=both]:data-[state=current]:border-[#525252] dark:data-[engine=both]:data-[state=current]:bg-[#2a2a2a] dark:data-[engine=both]:data-[state=current]:text-[#e5e5e5]">
            {label}
            <svg aria-hidden="true" width="12" height="12" viewBox="0 0 12 12" fill="none" className="shrink-0">
              <path d="M2.5 6h7M6.5 3l3 3-3 3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </a>;
  })}
    </div>;
};

<PlatformAvailability current="neki" postgres="/postgres/monitoring/query-insights" vitess="/vitess/monitoring/query-insights" />

Query Insights groups executions of the same normalized SQL into query patterns.
Use it to find queries that run frequently, consume a large share of database
time, read unnecessary rows, fan out across shards, or use more parallel workers
than expected.

Insights is available on every Neki cluster. From the same tab you can also
open [Anomalies](/docs/neki/monitoring/anomalies) and
[schema recommendations](/docs/neki/monitoring/schema-recommendations).

## Open Query Insights

From the [PlanetScale dashboard](https://app.planetscale.com), select a Neki
database and branch, then select **Insights**.

The page opens to the last 24 hours. Use the branch selector to choose the
branch you want to analyze.

## Explore query activity

The graph provides the following views:

* **Query latency** shows p50 and p95 latency by default. You can also display
  p99, p99.9, and maximum latency.
* **Queries** shows the query rate over the selected period.
* **Rows read** and **Rows written** show row activity over the selected period.
* The final tab graphs one additional query metric that you select.

Select a day from the previous seven days or use the **Last** menu to view the
last 15 minutes, 1 hour, 3 hours, 6 hours, 12 hours, or 24 hours. You can also
drag across a graph to examine a smaller time range, change the automatic
refresh interval, or save the graph as an image.

## Query patterns

Insights replaces literal values with numbered placeholders so executions that
differ only by their values can be grouped together. For example, these
executions belong to the same pattern:

```sql theme={null}
SELECT * FROM orders WHERE customer_id = 17;
SELECT * FROM orders WHERE customer_id = 42;
```

The normalized pattern uses a placeholder:

```sql theme={null}
SELECT * FROM orders WHERE customer_id = $1;
```

Normalized SQL is not the only thing that separates patterns. A router groups
executions by the database the session is connected to, what the statement's
names resolved to, and whether the execution ran on a primary or a replica. The
same SQL text can therefore appear as more than one row: once for each
`search_path` resolution that produced a different set of relations, and again
for the replica traffic that ran it.

### Pattern cardinality

Each router tracks up to 2,000 patterns per aggregation interval. Additional
patterns are combined into an overflow entry, so not every distinct pattern is
listed. A branch that runs many distinct statement shapes, such as generated
SQL with inlined literals that normalization cannot collapse, reaches this
limit most often.

The table below the graph summarizes each pattern. Use the **Overview**,
**Data**, **Resources**, and **Performance** presets to switch between related
columns, or select **Custom** to choose columns individually. Numeric columns
can display sparklines for the selected time range.

The default **Overview** preset includes the query, percentage of runtime,
execution count, total time, p50 and p99 latency, rows read, and the ratio of
rows read to rows returned.

### Table and schema names in Neki

A Postgres query can use an unqualified table name, such as `orders`, while
`search_path` determines the schema that Postgres actually uses. Four columns
describe the result of that resolution:

| Column              | Example              | Meaning                                                          |
| ------------------- | -------------------- | ---------------------------------------------------------------- |
| **Table**           | `orders`             | The relation name without its database or schema.                |
| **Qualified table** | `appdb.sales.orders` | The complete resolved relation name, as `database.schema.table`. |
| **Table schema**    | `appdb.sales`        | The database and schema of each resolved relation.               |
| **Schema**          | `appdb.a91f4c02`     | The connection grouping the pattern was recorded under.          |

For example, `SELECT * FROM orders` can resolve to `appdb.sales.orders`. Use
**Qualified table** when you need to know which database object Neki queried. If
a pattern accesses multiple tables, **Qualified table** and **Table schema**
list a value for each of them.

**Schema** is not a PostgreSQL schema name on Neki. Its value combines the
connected database with an identifier for what the statement's names resolved
to, so two rows with the same SQL and different **Schema** values resolved to
different relations. Read the actual schema from **Table schema** or
**Qualified table**.

### Available query statistics

The Neki query table can show the following groups of statistics. A column is
shown only when its data is collected for the selected branch.

| Group           | Statistics                                                                                                                                     |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Query identity  | Query, Schema, Table schema, Qualified table, Table                                                                                            |
| Time            | % of runtime, % of CPU time, % of IO time, Count, Total time, CPU time, I/O time, Last run                                                     |
| Latency         | p50 latency, p99 latency, Max latency                                                                                                          |
| Rows            | Rows read, Rows read per query, Rows returned, Rows returned per query, Rows affected, Rows affected per query, Rows read / returned           |
| Routing         | Shard calls per query, Max shard calls per query, Parallel workers per query                                                                   |
| Postgres blocks | Block cache hit ratio, Blocks hit, Blocks read, Blocks dirtied, Blocks written                                                                 |
| Network         | Bytes returned, Bytes returned per query, Max bytes returned per query, Bytes received, Bytes received per query, Max bytes received per query |

**Shard calls per query** is the average number of shard dispatches per
execution of the pattern, and **Max shard calls per query** is the highest
number recorded for a single execution. A query scattered to four shards
records four shard calls. Several statements produced by a rewrite within one
dispatch count as one shard call, and repeated dispatches to the same shard
count separately, so these metrics are not a count of distinct shards.

Buffered or replayed work and failed scatter executions can make the reported
count lower than the work performed.

A value greater than one indicates either multi-shard execution or repeated
work on a shard. Review the query plan and the database's
[data topology](/docs/neki/data-topology) when investigating an unexpected value.

**Parallel workers per query** is the average number of Postgres backend
processes that executed the statement, counted across every shard it reached.
The count includes the leader process, so a statement that ran without
parallelism on a single shard reports one, and a statement that reached several
shards adds up each shard's processes. Parallel maintenance workers, such as
those used by an index build, count the same way. This describes process use
inside Postgres and is separate from the number of shard calls.

## Filter query patterns

The filter field accepts free text and named terms. Select **SYNTAX** beside the
field to see the operators the dashboard supports.

```text theme={null}
statement_type:select
table:orders
table_schema:appdb.sales
multishard:true
indexed:false
query_count:>5000
p50:<25
p99:>250
max_latency:>1000
index:orders_customer_id_idx
```

### Filter on tables and schemas

Four named terms filter on the relations a pattern resolved to. All of them
match case-insensitively.

| Term                       | Matches                                                                                                                                                                                                             |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `table:orders`             | Any resolved relation named `orders`, in any database and schema.                                                                                                                                                   |
| `table:sales.orders`       | A resolved relation whose qualified name ends in `sales.orders`. A fully qualified value, such as `table:appdb.sales.orders`, narrows the match further.                                                            |
| `table_schema:appdb.sales` | Patterns that resolved a relation in the `sales` schema of `appdb`. Neki records this value as `database.schema`, so both halves are required.                                                                      |
| `schema:appdb.a91f4c02`    | The connection grouping described in [Table and schema names in Neki](#table-and-schema-names-in-neki). This term requires the whole value, so copy it from the **Schema** column rather than typing a schema name. |

`qualified_table:orders` finds patterns that name `orders` with an explicit
qualifier in their SQL. It accepts a bare relation name only, so use `table:`
when you want to filter by a database or schema prefix.

### Free text

A term with no name matches the normalized SQL with a case-insensitive
substring comparison. Free text does not search the resolved table metadata, so
`appdb.sales.orders` finds only patterns whose SQL text contains that string,
not every pattern that resolved to that relation. Use `table:` or
`table_schema:` for the resolved relation instead.

A named term whose value the term type rejects becomes free text as well. Because
`qualified_table:` accepts only a bare relation name,
`qualified_table:appdb.sales.orders` searches SQL text rather than table
metadata.

### Other filter behavior

Latency filter values are in milliseconds. Wrap text in double quotes for an
exact string match, and prefix a term with `!` to exclude it. Tag filters are
also available when query dimensions are enabled for the branch.

## Query-pattern details

Select a query pattern to open its detail page. The page shows the normalized
SQL and graphs the selected pattern separately from the rest of the branch. It
also provides summary statistics for count, total time, rows read relative to
rows returned, p50 latency, p99 latency, and errors.

Depending on the statement and the data collected for the branch, the detail
page can also show:

* Index usage over the selected time range.
* Notable executions that were slow, read a large number of rows, or returned
  an error.
* Query tags and their values.
* An optional AI-generated summary of the normalized query. See
  [How PlanetScale uses AI](/docs/how-we-use-ai).

## Anomalies and schema recommendations

Insights also surfaces two related views for every Neki cluster:

* [Anomalies](/docs/neki/monitoring/anomalies) flags periods when a high share of
  queries run slower than their established baseline.
* [Schema recommendations](/docs/neki/monitoring/schema-recommendations) suggest DDL
  that can improve performance, reduce storage, or prevent ID exhaustion.

From **Insights**, open **Anomalies** or **View recommendations**.

## Need help?

Get help from [the PlanetScale Support team](https://planetscale.com/contact?initial=support), or join our [Discord community](https://pscale.link/community) to see how others are using PlanetScale.
