
Telemetry
When you operate a complex database system, you often need to know what is happening under the hood of the database engine, so you can optimize your queries and so on. Telemetry is a toolset that helps you to understand how your actions are planned and executed.
Query telemetry
- argument:enum(TIMINGS|PLAN)
- How much detail to profile at, TIMINGS being the default and an implicit argument — queryTelemetry() and queryTelemetry(TIMINGS) are the same constraint, and both print as the former. PLAN additionally returns the formula plan the query engine built — see below. The two are levels rather than flags: a profile carries the timings, or the timings and the plan.
The query telemetry object represents a single executed operation with possibly nested other operations and consists of the following data:
- operation
- start
- When this step began, in nanoseconds. This is not a wall-clock timestamp and must never be rendered as a date.Embedded, it is a raw monotonic counter reading with no defined epoch — meaningful only relative to another reading taken in the same JVM.
- startedAt
- The wall-clock instant at which the query began. Carried by the root step only — it is what anchors the whole tree in time, so a profile can be correlated with logs, traces or another query. Every other node reports
null, and its own wall-clock position isstartedAtplus that node'sstartoffset. - steps
Internal steps of this telemetry step (operation decomposition). Same structure as the parent telemetry object.
- arguments
Arguments of the processing phase — for example, which index was selected and at what estimated cost.
- spentTime
Duration in nanoseconds, covering this step and everything nested below it.
- plan
- Structure of the formula the engine built for this phase — see below. Present only when the query asked for it, and then only on the phases that own a formula.
- metrics
- Typed numeric measurements the engine computed while answering the query. Where the durations above say where the time went, these say why — see the table below. Recorded on the root step only, so every other node reportsno metrics at all.
The metrics themselves:
| Metric | Meaning |
|---|---|
| estimatedCardinality | How many records the planner expected the filter to match |
| actualCardinality | How many records the filter really matched, before paging |
| estimatedCost | Cost the planner estimated for the formula it chose |
| actualCost | Cost that formula really incurred once it ran |
| recordsReturned | How many records were handed back, i.e. the size of the requested page |
| ioFetchCount | How many times the storage was read while assembling the response |
| ioFetchedSizeBytes | How many bytes were read from the storage |
| prefetched | Whether the planner filtered over prefetched entity bodies instead of consulting indexes — read this before interpreting a plan, see below |
The formula plan
- every index-selection alternative carries the candidate the planner costed, including the ones that lost
- the root carries the plan that actually ran
That first point is the one worth the trouble. Every engine will tell you what it did; very few will tell you what it considered and rejected, and at what estimated cost. That is the information that explains a plan which looks wrong.
Each node of the plan reports:
| Property | Meaning |
|---|---|
| id | Identity of the formula instance, stable across its occurrences in the plan |
| refTo | Set only on a repeat occurrence, pointing back at the id that describes it |
| hash | Structural hash — what the cache keys on |
| description | What the formula is, in human-readable form |
| estimatedCost | What the planner expected this part to cost |
| actualCost | What it really cost, or absent if it never ran |
| resultCount | How many records it produced, or absent if it never ran |
To demonstrate the information the query telemetry is providing, we will use the following query that filters and sorts entities:
The result contains query telemetry and some products (which we omitted here for brevity):
