---
title: Database Monitoring Workbench
description: >-
  Develop and test changes against an ephemeral, production-like Postgres
  database built from the schema and statistics that Database Monitoring
  collects.
breadcrumbs: Docs > Database Monitoring > Database Monitoring Workbench
---

> For the complete documentation index, see [llms.txt](https://docs.datadoghq.com/llms.txt).

# Database Monitoring Workbench

{% callout %}
##### Join the Preview!

Database Monitoring Workbench is in preview. Use this form to request access.

[Request Access](https://app.datadoghq.com/forms/share/45a8d961134a872c7118d345cff413cfd3d88bc1f1558f76bb5067a85ae43118)
{% /callout %}

## Overview{% #overview %}

Database Monitoring Workbench is an ephemeral, production-like Postgres database that you can use to develop and test changes. It is built from the schema and statistics that Database Monitoring collects, so it matches the shape of your production tables, indexes, row counts, and major version. Datadog never copies or transmits the data in your tables. Workbench is automatically populated with synthetic data generated from the column and table statistics that Database Monitoring collects. If you prefer, you can manually populate it with your own data.

This page explains how to:

- Connect to a Workbench instance with MCP, the API, or a SQL client
- Compare query plans and test index and migration changes
- Catch plan regressions in CI
- Experiment with data model changes

## Requirements{% #requirements %}

- A Postgres database monitored by [Database Monitoring](https://docs.datadoghq.com/database_monitoring.md).
- [Schema collection](https://docs.datadoghq.com/database_monitoring/schema_explorer.md) enabled for the specific logical database, not only the instance.
- The **Database Monitoring Read** permission. See [Role-based access control](https://docs.datadoghq.com/account_management/rbac/permissions.md) for how to manage permissions.
- Workbench enabled for your organization. To request access, use the preview form at the top of this page.

## Connect to Workbench{% #connect-to-workbench %}

### Connecting with the MCP server{% #connecting-with-the-mcp-server %}

Use the [Datadog MCP server](https://docs.datadoghq.com/mcp_server.md) to create and manage Workbench instances from a coding agent. To add it to your agent, see [Set up the Datadog MCP server](https://docs.datadoghq.com/mcp_server/setup.md). The MCP server exposes Workbench as three tools:

| Tool                                | Description                                                                  |
| ----------------------------------- | ---------------------------------------------------------------------------- |
| `create_datadog_database_workbench` | Builds a sandbox from a monitored database and waits for it to be ready.     |
| `get_datadog_database_workbench`    | Checks whether a sandbox is ready.                                           |
| `delete_datadog_database_workbench` | Deletes a sandbox, revoking its connection string and releasing its compute. |

After the agent creates an instance, it can read your schema, run statements, read query plans, add an index, and read the plan again. It works against your real schema, indexes, and row counts.

{% video
   url="https://docs.dd-static.net/images/database_monitoring/database_monitoring_workbench/workbench_mcp_index_demo.mp4" /%}

### Connecting with the API{% #connecting-with-the-api %}

Use the Workbench API to create a Workbench instance from a script or CI job.

To create an instance, send a `POST` request that names the monitored database:

```shell
curl -X POST "<YOUR_DATADOG_API_URL>/api/unstable/databases/workbench/session" \
  -H "DD-API-KEY: <DATADOG_API_KEY>" \
  -H "DD-APPLICATION-KEY: <DATADOG_APP_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "database_instance": "orders-db-primary",
    "database_name": "shop"
  }'
```

The application key must belong to a user or service account with the **Database Monitoring Read** permission. If Workbench is not enabled for your organization, the API returns `403`.

| Parameter         | Description                                                                                                            |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| database_instance | The name of the instance in Database Monitoring.                                                                       |
| database_name     | The name of the logical database inside the `database_instance`.                                                       |
| populate          | Set to `false` to create the instance without generated data. To load your own data, see Connecting with a SQL client. |

The request returns `202 Accepted` with the instance ID, its status, and a Postgres connection string:

```json
{
  "id": "workbench-123",
  "status": "pending",
  "connection": {
    "dsn": "postgres://workbench:<TOKEN>@<WORKBENCH_HOST>:5432/bench?sslmode=require"
  }
}
```

Creating an instance is asynchronous. To check readiness, send `GET /api/unstable/databases/workbench/session/{id}` until `status` is `ready`. The response also includes `expires_at`.

Instances expire after the number of seconds set in `ttl_seconds`, which is 1,800 seconds (30 minutes) by default. You cannot set the TTL in the request.

To delete an instance, send `DELETE /api/unstable/databases/workbench/session/{id}`. A successful request returns `204`.

{% alert level="danger" %}
The connection string is a live database credential. Treat it as a secret: do not commit it, log it, or paste it into a shared channel. Delete the instance when finished. Expiry or deletion closes connections and discards all data.
{% /alert %}

### Connecting with a SQL client{% #connecting-with-a-sql-client %}

Use the connection string from the MCP server or the API with any Postgres client, such as `psql`, a GUI, or your ORM's test harness.

```shell
psql "postgres://workbench:<TOKEN>@<WORKBENCH_HOST>:5432/bench?sslmode=require"
```



The instance is writable, so you can create an index, re-run `EXPLAIN`, and compare the plans. For examples, see How to use Workbench. When the instance expires or you delete it, open connections close and in-flight queries can fail.

By default, Workbench populates the instance with synthetic data. To use your own data instead, create the instance with `"populate": false` in the API request. Then load your data with `INSERT`, `COPY FROM STDIN`, or the `psql` `\copy` command, and run `ANALYZE` afterward. Instance resources and the TTL limit how much data you can load.

## How to use Workbench{% #how-to-use-workbench %}

Create an instance with the MCP server or the API, and connect to it with a SQL client. Then use it for the tasks in this section.

### Compare query plans before and after a change{% #compare-query-plans-before-and-after-a-change %}

Check a query's plan when you write it, before you open a pull request. You can also use these steps to test a rewrite after you find a slow query in Database Monitoring. Create the instance for the database the query ran on.

Run `EXPLAIN` against the instance and read the plan against production-like row counts and cardinalities:

```sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders
WHERE customer_id = 42 AND created_at > '2026-01-01'
ORDER BY created_at DESC;
```

If the plan shows `Sort -> Seq Scan on orders`, the query scans the whole table. Add the composite index in the instance, re-plan, and confirm the plan changes to an index scan:

```sql
CREATE INDEX idx_orders_customer_created ON orders (customer_id, created_at DESC);
```

Then ship the index with the query. For a slow query, compare the plans before and after your rewrite, and bring the result to the pull request. You can test rewrites without touching production or requesting access to its data.

### Check which queries depend on an index{% #check-which-queries-depend-on-an-index %}

Dropping an unused index saves write throughput and storage, but dropping one that a query depends on can cause an incident. Test the drop in an instance first. Dropping an index there does not affect production.

1. Get your top queries from [query metrics](https://docs.datadoghq.com/database_monitoring/query_metrics.md).
1. Run `EXPLAIN` on each query and save the plan.
1. Drop the index in the instance.
1. Run `EXPLAIN` on each query again and compare the plans.

```sql
EXPLAIN SELECT id, total FROM orders WHERE status = 'pending' ORDER BY created_at;
--  Index Scan using idx_orders_status on orders  (cost=0.42..88.20 rows=312 width=20)

DROP INDEX idx_orders_status;

EXPLAIN SELECT id, total FROM orders WHERE status = 'pending' ORDER BY created_at;
--  Seq Scan on orders  (cost=0.00..14200.00 rows=312 width=20)
```

A query whose plan falls back to a sequential scan depends on the index. Bring those plans to your review instead of relying on subjective, untested assumptions. To test a new index instead, create it in the instance and re-plan your queries.

### Test a migration before you deploy it{% #test-a-migration-before-you-deploy-it %}

Migration problems often depend on table size and concurrent traffic, so a local test database can miss them. For example:

- A `CREATE INDEX` that should have been `CREATE INDEX CONCURRENTLY`, and holds a write lock for the length of the build.
- An `ALTER` that takes a stronger lock than expected on a table that is never idle.

To test a migration, run it against the instance with your migration tool, using the instance's connection string. While the DDL runs, query `pg_locks` to see which locks it takes. Afterward, re-plan your important queries to see how the new schema affects them.

A test migration takes less time than production, but produces the same structural outcome.

### Catch plan regressions in CI{% #catch-plan-regressions-in-ci %}

Use the API to add a plan check to your pipeline. For each pull request that touches SQL or schema:

1. Create an instance.
1. Poll until the status is `ready`.
1. Prepare the data.
1. If you compare plans before and after the change, capture baseline plans with `EXPLAIN`.
1. Apply the change.
1. Run `EXPLAIN` on your top queries.
1. Fail the build if a plan breaks one of your invariants.
1. Delete the instance, even if an earlier step fails.

Examples of invariants:

- No new sequential scan on a large table.
- No plan-shape change on a query in your critical path.
- No index dropped that is still in use.

The following script runs this flow in a CI job. It requires `curl`, `jq`, and `psql`, and these environment variables:

- `DD_API_KEY` and `DD_APP_KEY`: your Datadog API key and application key.
- `DD_SITE`: your Datadog site, such as `datadoghq.com` or `us3.datadoghq.com`. Defaults to `datadoghq.com`.
- `DB_INSTANCE` and `DB_NAME`: the `database_instance` and `database_name` to create the instance from.

```shell
#!/usr/bin/env bash
set -euo pipefail
API="https://api.${DD_SITE:-datadoghq.com}/api/unstable/databases/workbench/session"
AUTH=(-H "DD-API-KEY: ${DD_API_KEY}" -H "DD-APPLICATION-KEY: ${DD_APP_KEY}" -H "Content-Type: application/json")

# Create (returns 202 with id + connection.dsn)
resp=$(curl -sf -X POST "$API" "${AUTH[@]}" \
  -d "{\"database_instance\":\"${DB_INSTANCE}\",\"database_name\":\"${DB_NAME}\"}")
id=$(jq -r .id <<<"$resp")
dsn=$(jq -r .connection.dsn <<<"$resp")
trap 'curl -sf -X DELETE "$API/$id" "${AUTH[@]}" >/dev/null || true' EXIT  # always delete

# Wait for ready
for _ in $(seq 60); do
  status=$(curl -sf "$API/$id" "${AUTH[@]}" | jq -r .status)
  [ "$status" = ready ] && break; sleep 5
done
[ "$status" = ready ] || { echo "Workbench not ready: $status"; exit 1; }

# Apply the change, then EXPLAIN top queries
psql "$dsn" -v ON_ERROR_STOP=1 -f migrations/change.sql
psql "$dsn" -v ON_ERROR_STOP=1 -f ci/explain_top_queries.sql > plans.txt

# Fail on a broken invariant (example)
if grep -q "Seq Scan on orders" plans.txt; then echo "Plan regression"; exit 1; fi
```

In this example, `migrations/change.sql` contains your change, and `ci/explain_top_queries.sql` contains an `EXPLAIN` statement for each of your top queries. The last check fails the job if a plan includes a sequential scan on `orders`. Replace it with your own invariants.

### Experiment with data model changes{% #experiment-with-data-model-changes %}

Changing a data model is one of the riskiest operations you can run on a database. Splitting a table, changing a column type, or adding a foreign key can break queries, block reads and writes, or fail against production data. Most of these changes are hard to undo.

A Workbench instance gives you your real schema to experiment on. Try the change, run your joins and queries against it, follow the foreign keys, and see which tables are large and which are lookup tables. If something breaks, delete the instance and create another one.

With the default synthetic data, no privacy review is required, because Workbench contains none of your customer data.

## Limitations{% #limitations %}

Workbench answers questions about schema, query plans, and relative cost. It does not reproduce production hardware, data, or every schema object.

- **Only Postgres is supported.** Workbench supports Postgres 12 through 18. Other database engines are not supported.
- **Timings may vary.** Instances are small and not sized like your production hardware. Compare plan shape, row estimates, index usage, and before-and-after results, not absolute timings such as "this query takes 40ms." To benchmark a rewrite or index, use [Bits Database Optimization](https://docs.datadoghq.com/database_monitoring/bits_database_optimization.md).
- **Generated data is approximate.** By default, Workbench generates rows from collected statistics, so table sizes are close to production. Skew, column correlation, and value distributions can differ, which can change plans that depend on them. Populating Workbench with your own data avoids this.
- **Some indexes and foreign keys may be missing.** Workbench may skip an index or foreign key, or drop an expression that calls an unavailable function.
- **Views, functions, triggers, roles, and grants are not reconstructed.** Changes that depend on them do not reproduce accurately.
- **Instances are temporary.** Instances expire after 30 minutes by default, so re-create any that you need to keep. Schemas over a certain table count are not fully materialized.

Use Workbench to check structure, query plans, and order-of-magnitude effects. It does not cover other effects.

## Further reading{% #further-reading %}

Additional helpful documentation, links, and articles:

- [Database Monitoring](https://docs.datadoghq.com/database_monitoring.md)
- [Schema Explorer](https://docs.datadoghq.com/database_monitoring/schema_explorer.md)
- [Recommendations](https://docs.datadoghq.com/database_monitoring/recommendations.md)
- [Datadog MCP Server](https://docs.datadoghq.com/mcp_server.md)
