Understand query cost
Separate estimated work, measured database execution and connection timing.
Use Dashboards → Estimate query cost before opening a dashboard, or Query cost inside a dashboard with its current parameters. Opening or recalculating an estimate never executes the application query. Recalculation preserves the report, selected request, expanded details, and calculator inputs.
The summary separates two kinds of evidence:
- Before you run: PostgreSQL, MySQL and MariaDB plans estimate work for the selected parameters and limits. Planner units are never converted to milliseconds.
- Measured database work: a shared summary uses mean database execution time, execution count, observation period and scope. Ordinary runs improve the evidence; BytesMap never runs an extra copy of the application query just to measure it.
Recent measurements are preferred over retained server history. The Light / Moderate / Heavy rating uses the same fixed time and read bands across engines. Dashboard time is the sum of each distinct statement's mean, not an average of unrelated queries or dashboard wall-clock time. Partial measurements and failed requests stay explicit.
For multiple database requests, selection changes the plan and server history together. The chart/query/request counts still describe the whole dashboard. Technical plans, buffer activity, connection diagnostics, and the refresh calculator are initially collapsed.
A plan's cost units are relative work estimates, not milliseconds or a percentage of production capacity. They are not converted into a runtime or slowdown percentage. The server's historical average is also not a guarantee about the next run: parameters, plans, cache state, contention, and concurrent traffic can differ. CPU utilization, physical I/O, and slowdown of other production traffic require separate resource and workload measurements. No percentage is inferred from the number of charts or requests.
PostgreSQL plan and server evidence
Plans use EXPLAIN (FORMAT JSON, ANALYZE FALSE, COSTS TRUE, VERBOSE TRUE). The wrapper,
bound parameters, limits, and truncation sentinel match normal execution. A bound catalog
lookup adds relation cardinalities and sizes without counting application rows.
On PostgreSQL 14+, the plan's query identifier can match retained pg_stat_statements
statistics. This requires the extension loaded on the server and installed in the source
database, query identifiers enabled (compute_query_id = auto or on), and statement
tracking enabled. BytesMap does not install extensions, restart servers, reset statistics,
or change production configuration. Missing telemetry remains explicitly unavailable;
a missing retained entry says No recorded database runs rather than zero cost.
The adapter:
- Preserves EXPLAIN's signed 64-bit query identifier without JSON number rounding. Its query-local JSON parser does not change normal application JSON results.
- Reads
pg_stat_statements(false)for that identifier, current database, current role, and top-level statements. Query text is not retrieved. Values are bound, and the extension's schema comes from the catalog and is quoted as an identifier. - Uses
mean_exec_timeand the server's retained execution counters. These are PostgreSQL executor timings, not planning time or BytesMap's round trip. They include server waits, potentially including waits while delivering results to the client. - Labels the scope as same query shape. PostgreSQL normalizes constants, so parameter values and row-limit values can differ. Other clients using the same database role may contribute. This is not exact-parameter or BytesMap-only execution history.
- Reads existing evidence only during explicit estimation, with no extra instrumentation requests on Run/Refresh. Server collection happens within PostgreSQL.
- Uses a savepoint so missing permissions, disabled extensions, version incompatibilities, or an optional statistics timeout do not discard an otherwise valid plan. Cancellation still aborts the operation.
Newer PostgreSQL versions expose per-statement statistics epochs. A min/max-only reset
can leave a zero range or a range from a different period than the average; those ranges
are hidden. Older versions show an unknown statistics start time. On servers exposing per-statement
stats_since, BytesMap saves up to 30 distinct counter snapshots during explicit cost
inspection, using only those from the last 7 days. The recent mean is
(total_exec_time_end - total_exec_time_start) / (calls_end - calls_start), never an average
of cumulative means. The UI shows the actual observation interval, which can be shorter
than seven days. Same-counter inspections do not consume extra snapshot slots or add calls.
Only a contiguous, monotonic, same-epoch interval is used. Resets, stale snapshots and missing epochs fall back to Retained server history. Cumulative min/max and percentiles cannot be differenced: the recent interval omits them. Older servers retain their honest historical average rather than receiving an invented rolling measurement. The timestamp says Checked, not last executed, because PostgreSQL does not expose the latter here.
Details show mean shared-buffer reads, temporary data writes, and cache hit ratio. A PostgreSQL buffer miss can still hit the operating system cache, so these are not physical disk reads. The cache percentage has that specific denominator and never represents CPU, disk utilization, or production impact.
MySQL and MariaDB measurements
Normal execution reads the completed statement from Performance Schema using a unique marker on the same pinned connection. The observation includes source execution time, rows examined, rows sent, locks and temporary tables. Missing instrumentation remains unavailable.
The shared summary averages successful source measurements from the latest 30 request attempts within seven days. It includes mean examined rows, measured min/max, count and last measured execution time. At least 20 source measurements are required to show the empirical nearest-rank 95th percentile; smaller samples show the observed range and an early-measurement hint. Failures, cancellations, cache hits and missing instrumentation never become zero-time runs. Exact inputs and execution limits remain separate; changed inputs start new history.
Request diagnostics
The previous client timer remains under Details → Connection timings. It is never used as PostgreSQL execution time or as a fallback when server statistics are absent. It includes driver queue, the database request, network, and result transfer, and excludes connection establishment, local flow transformations, and rendering. The client and server histories have different scopes; their averages are not subtracted to invent network time.
The shared execution path observes main statements, grand totals, and explicit counts. Only actual adapter requests contribute: cache hits, schema probes, and estimates do not. Failures, timeouts, and cancellations keep their outcomes and are not counted as successful timings. Diagnostics show the median of successful matching requests and any failed attempts.
History matches the compiled SQL and typed bound values, source and organization, connection settings, row/byte ceilings, and timeout. Changed parameters or limits start separate history. Opaque Mongo prerequisites cannot be matched by an incomplete serialization. The display uses up to 30 attempts from the last 7 days; stale or unreadable history is labeled.
query_runtime in BytesMap's internal metadata database stores only a SHA-256 fingerprint,
source/organization IDs, timestamps, outcomes, counts, durations and optional server measurements. It stores no SQL, parameters,
credentials, or results. The additive startup migration supports PostgreSQL metadata and
embedded PGlite. Writes happen off the response path with atomic bounded append; immediately
following estimates await matching pending writes. Storage failures never fail chart execution.
Each source retains at most 256 signatures and recording prunes signatures older than 30 days.
Deletion cascades and reads/writes are organization-scoped.
query_execution_history stores PostgreSQL counter snapshots separately, keyed by a hashed
source/connection identity and the lossless normalized query ID. This scope deliberately spans
parameter values. It has the same 256-signature / 30-day retention limits and cascading tenant
isolation. Counter updates are atomic and idempotent for unchanged source statistics. Metadata
storage errors preserve the current plan and retained source evidence. Startup migration is
additive for both PostgreSQL metadata and embedded PGlite.
The report timestamp is taken at completion, independently of the fixed time used to resolve query parameters. Newly collected server evidence therefore cannot appear to come from the future relative to the report.
Scope and limits
Sequential scan estimates include rows filtered out. A filtered count can examine a million rows to return one. Simple unfiltered limits can stop early; blocking sorts and aggregates consume their input. Filtered limits and offsets retain an upper scan scope. Unknown loops, bitmap rechecks, and index residual filters remain unknown. Catalog sizes are table footprints, not bytes read; estimates depend on the freshness of PostgreSQL statistics.
One report describes the first page of one refresh:
- Charts sharing a saved query reuse one execution. Eight charts backed by one query have the same database plan as that query backing one chart.
- Grand totals and explicit counts include their additional statements.
- Flows include distinct native/table reads and actual column selections and limits. Local transformations and chart rendering are outside the database estimate.
- Live resource metrics use separate catalog reads and are explicitly excluded.
- Failed or unsupported plans remain unavailable; partial totals only include known work.
The calculator models fresh executions without changing dashboard settings:
statements/hour = statements/refresh × 3,600 ÷ interval_seconds × open_sessions
Actual cache reuse and user activity can reduce the workload. Concurrent request durations are not added into dashboard loading time. Selection, expansion, and calculator edits make no additional API or database requests.
API and shared ownership
POST /api/dashboards/:id/cost accepts { params?: {...} }.
POST /api/queries/:id/cost accepts saved-query run options except explain.
Flow conditions and limits stay in nodes as on the run path. MCP's dashboard_cost and
query_cost use the same endpoints.
All queries and sources are authorized before planning. Flows require SQL-run permission on each source. Planning uses a read-only transaction, at most three seconds per statement, and at most one second of lock wait; a tighter source timeout wins. The HTTP operation has a 20-second budget and cancellation support.
Evidence types and buffer calculations live in @bytesmap/core, PostgreSQL collection in
@bytesmap/adapters, and rendering in @bytesmap/ui/patterns/query-cost for both the app
and SDK hosts. The API owns authorization and client diagnostic persistence. The existing
run option explain: true returns compiled SQL with executed results and is unrelated to
this non-executing cost endpoint.
Validation
Unit checks cover lossless identifiers, role/database scoping, optional telemetry failures,
resets, buffer ratios, limits, and the absence of extra executions. A disposable PostgreSQL
fixture with pg_stat_statements verifies that EXPLAIN matches real server execution
counters, parameter changes share the documented normalized scope, and recalculation never
increments execution counts. Set BYTESMAP_QUERY_EXECUTION_TEST_URL to that isolated fixture
for packages/adapters/src/postgres-execution.test.ts.
Dashboard browser tests cover light/dark/dim, narrow screens, keyboard navigation, loading, refresh and error retention, local request selection, absent server history, and keeping client timings separate from server execution evidence.
Local demo
bun scripts/seed-query-cost-demo.ts
bun scripts/register-query-cost-demo.tsThe seed starts the dedicated bytesmap-query-cost-demo container on 127.0.0.1:54384.
It adds 50,000 customers, 500,000 orders, and 1,000,000 events to bytesmap_cost_demo,
schema cost_demo, creates lookup indexes, and runs ANALYZE. Repeat runs add missing
fixture rows without deleting data or touching other Postgres containers.
Registration needs the normal local API on port 4077 and a configured local workspace. It adds a read-only connection and Production query cost demo with six charts: indexed lookup, event scan, join, shared query, grand totals, and bounded visual flow. Existing demo definitions are preserved on repeat runs.
Local-only reader credentials: bytesmap_cost_reader / local_reader_only.
Local-only administrator credentials: bytesmap_demo / local_demo_only.
BYTESMAP_QUERY_COST_TEST_URL=postgres://bytesmap_cost_reader:[email protected]:54384/bytesmap_cost_demo bun test packages/adapters/src/postgres-plan.test.tsThe integration check explains pg_sleep(5) under a 500ms timeout, proving the statement
is not executed. It also checks indexed versus sequential-scan cost and real row limits.