Skip to content

Monitoring a query engine that stores nothing

Nodes, sessions, slow queries and per-catalog connectors all report, while database size is N/A rather than zero, because there is no storage to measure.

The monitoring dashboard has seven tabs, and on Apache Trino several of them answer with a refusal. A coordinator can observe the nodes in the cluster, the statements in flight and the history it still remembers; it observes no bytes on disk, because it owns none. Every table Trino can name belongs to a system behind a connector, and so does that table’s footprint.

So the question is not which panels work, but what the empty ones are allowed to say. A blank figure has two readings, and they are not the same fact: nobody measured this and this measures zero. For storage on Trino it is always the first.

Where each panel reads from

Nothing here is scraped from the web UI. Every reading is a statement sent down the client protocol, POST /v1/statement, against system, jmx and the information_schema of the catalog the connection pins.

Panel Statement source What comes back
Overview system.runtime.nodes, information_schema.tables, system.runtime.queries Version, table count, statements in flight
Overview uptime jmx.current."java.lang:type=runtime" The coordinator JVM’s uptime
Sessions system.runtime.queries, non-terminal states The statements running now
Queries system.runtime.queries, FINISHED rows by elapsed time The coordinator’s recent history
Storage system.metadata.catalogs One row per catalog, location = its connector
Tables SHOW STATS FOR <table>, one statement each Row counts and logical sizes, or a refusal
Performance jmx.current."trino.execution:name=querymanager" Queries per second

The version field is the bare string 476 - no product name, no semver, because that is what system.runtime.nodes.node_version holds. Uptime needs the jmx catalog configured: nothing in system.runtime records a start time, and /v1/info, which does report one, is not a statement. A cluster without jmx reads unknown there rather than failing the overview: CATALOG_NOT_FOUND maps to unknown-object, one of the two categories that mean this surface is not available here. Every other failure propagates, since a timeout hidden behind an empty panel is hidden forever.

The index reading is the one place a zero is honest: it returns an empty list and sends no statement at all. Trino’s information_schema holds exactly eight views, with no table_constraints and no key_column_usage, so no connector can declare a key through it. Zero is the true count, and it cannot vary with the connection, so there is nothing to ask.

Sessions and slow queries on a coordinator

A sessions tab on a PostgreSQL connection lists connections. Trino has none: the client protocol is stateless HTTP, each statement is its own exchange, and there is no session object anywhere to count. The panel shows statements in flight instead, the nearest true thing.

That mapping leaves one column deliberately blank. system.runtime.queries has no catalog column, so the per-session database field is an empty string rather than the catalog this connection pinned - filling it in would credit somebody else’s statement with a catalog it may never have touched.

The queries tab inherits two more honest blanks. Call count is 1 and row count is 0 on every row, because that table records executions rather than statements: nothing aggregates, total time equals average time, and there is no row-count column at all. The history behind it is a bounded in-memory window a restart empties, and the panel’s empty state says so - these stats hold only what this coordinator still remembers.

The read also sees itself, as a RUNNING row, and that is not filtered: the coordinator really is executing that statement.

No database size, and why zero would be a lie

Trino publishes exactly one byte figure, data_size from SHOW STATS. It is per table, an estimate, and null for every fixed-width column - measured on tpch.tiny.region, 34 and 330 for the two varchars and null for the bigint. There is no catalog of sizes to aggregate, and no capacity a percentage could be a fraction of.

So the overview’s database size is the string N/A, and the numeric field beside it is not written at all: the key is absent from the object rather than present holding 0. A 0 is a measurement, and the storage tab formats whatever number it is given, so a fabricated 0 B beside a 0.0 percent breakdown is exactly what this refusal avoids. The rows name the catalogs and their connectors instead, which is where the data is.

The same rule caught the panels above it. The overview and performance tabs once read two absent fields as zero, drawing a 0 percent bar rated Poor in red beside a 0 deadlock count badged Healthy - a fault and a clean bill of health for figures nobody measured. Both now read N/A beside the words Not measured. Cache hit ratio is scored critical below 80, so a neutral 0 there would have painted every healthy cluster red.

One statement per table, and the cap that follows

SHOW STATS takes one table, or one query. There is no batch form, so describing N tables costs N statements, and the pass is bounded at 25 of them.

That bound used to truncate: the reading described the first 25 tables of the scope, returned those rows, and no consumer could tell. The result is a plain array, and 25 rows out of 500 are indistinguishable from a catalog holding exactly 25 - a cap read as a count is the absence lie one size up. An oversized scope is refused outright now, which is also cheaper: the table list has already answered the question, so no SHOW STATS is sent.

Measured against the probe cluster:

Scope User tables Reading
tpch 72 Refused, naming 72
tpch.tiny 8 8 rows - lineitem 60,175, nation 25
system 26 Refused, naming 26
tpcds 250 Refused, naming 250
jmx.current 379 Refused, naming 379
memory 0 [] - a measurement, not a refusal

The limit, stated plainly: the tables panel describes at most 25 tables and goes absent with a stated sentence above that, database size and per-catalog size are N/A because this engine stores nothing, and the performance tab reports one field with every other left absent.

The bound applies to what was asked for, not to what the catalog holds, so a schema inside it is describable in a catalog of any size - which is how tpch.tiny stays readable while tpch is refused. The refusal says how many of the schemas the catalog’s tables are in would fit the bound, or that none would, rather than offering a remedy that fails; each version ends with the statement no bound applies to, SHOW STATS FOR "<catalog>"."<schema>"."<table>".

Metrics that belong to the connector behind the catalog

The performance tab reports queries per second and nothing else, and each absence is a different impossibility rather than a gap: Trino runs no transactions, holds no buffer pool because it holds no pages, takes no locks so counts no deadlocks, writes no checkpoints, and its caches belong to the connectors, which publish no hit ratio through it.

The one field it does report comes from the coordinator’s own JMX counter rather than from counting rows of query history over a window: that history is bounded and trimmed, so a derived rate would fall towards zero on a busy cluster the moment the window wrapped.

The same ownership argument settles maintenance. Trino has no vacuum operation, because it owns no storage to reclaim, and it computes no statistics of its own. ANALYZE is in the grammar, but every connector decides for itself whether it implements it, and the memory connector answers This connector does not support analyze. A button that always fails is worse than a stated reason. The maintenance toolkit is admin-only in any case, and on this engine no maintenance control is rendered at all.

A panel can be absent with a sentence rather than empty because of the capability declarations each engine publishes, and Trino’s line on the engine list says the rest: it queries catalogs, and the storage behind them is somebody else’s.