doc: design for reporting database time to SQL clients - #38443
Conversation
Add a design document for reporting database time to SQL clients: how long Materialize worked on a statement, delivered as an opt-in notice on pgwire and the HTTP/WebSocket API, and rendered by the Console beside the round-trip figure it already shows. The substance is where a statement actually finishes, which is not one moment. Row-returning statements end at the last row accepted by the transport. Writes only stage at their completion message, so a write in an implicit transaction emits after the commit instead. Documents two limitations rather than implying a precision the number does not have: client backpressure lands inside the interval, and cursors report elapsed time spanning client round trips.
4f87268 to
da732a8
Compare
|
Yah, i can totally build out a prototype if we think directionally this is the right way to do thing. The statement logging is only possible get to a state where we feel comfortable with not having to sample |
|
quick corrections DDL is covered. The exclusions are Parse and bind are measured, not assumed small. Across prod us-east-1 over six hours, |
|
If we wanted to estimate it from the client side, we could send a ping over the ws connection and use that as an estimate for network latency. We'd then subtract that number from the reported time on the SQL query response. It would be an educated guess instead of an accurate measurement, but it would be something. We could label it with that caveat in the UI. It would be a pretty easy lift from a LOE perspective, I think. |

Add a design document for reporting database time to SQL clients.
Motivation
The Console SQL Shell reports one number,
Returned in 148ms. It is a client-sideround trip, and it is correct:
calculateCommandDuration(
console/src/platform/shell/timings.ts:28-50) subtracts twoperformance.now()stamps. It is also unable to answer the question users actually ask, which is how
much of that time was Materialize.
The gap is not Console-specific. A
psqluser and a dbt run are equally unable toseparate their latency from ours.
Design doc only. No behaviour change, no code.
Description
Report database time: how long Materialize worked on a statement. Clients opt in
via a session variable and receive it as a notice with a dedicated SQLSTATE, so this
is a platform capability rather than a Console feature. The Console renders it beside
the round trip it already shows:
Why not time to first row. The adapter already records
time_to_first_row_seconds(src/adapter/src/client.rs:2222-2229) and discards theper-request value, which makes it the tempting choice. It is the wrong quantity: a
query returning 100,000 rows can reach its first row in 2ms and then occupy the
server for hundreds of milliseconds streaming the rest. It ships in the payload for
diagnosis, not as the headline number.
Where a statement finishes is not one moment. Row-returning statements end when
the last row is accepted by the transport. Writes are the interesting case: they only
stage at their completion message (
add_transaction_opsatsrc/adapter/src/coord/sequencer.rs:948, returning immediately at:974), and thedurable commit runs afterwards (
src/pgwire/src/protocol.rs:1355-1359,src/environmentd/src/http/sql.rs:1596-1603). Anchoring a write to its completionmessage would report staging cost only, so an
INSERTthat spent most of its time ingroup commit would render
2ms database time. Writes in an implicit transactiontherefore emit after the commit. In an explicit transaction the commit belongs to
COMMIT, which reports it.Delivery. Every site queues via
Session::add_noticeand flushes explicitly,rather than sending a
BackendMessagedirectly.Session::notice_filter(
src/adapter/src/session.rs:558) is private tomz_adapterand only runs on thequeue path, so a direct send would bypass
client_min_messagesand could not fix itfrom
mz_pgwire. The pre-existing statement-scoped flushes cannot carry the value,because all five run before it exists.
A stated limitation. Both transports flush inside the row loop and await the
socket (
protocol.rs:2655;sql.rs:1218-1219, whose comment describes the intent as"so a slow client applies real backpressure"), so client backpressure lands inside
the measured interval. Database time therefore degrades toward round-trip time
exactly when the network is slow. Excluding socket waits would mean instrumenting
around every flush, a materially larger change than the one clock read this design
needs. This version ships the last-row anchor and states the limitation in the
tooltip and the docs rather than implying a precision it does not have.
Cursors are likewise outside the guarantee:
execute_startedis stamped once andresumption does not re-stamp (
protocol.rs:1768-1779), so aFETCHthat exhausts astream reports elapsed time spanning client round trips. The Console is unaffected
(
Fetchis rejected on HTTP and WebSocket,sql.rs:1516-1520) butpsqland dbt usecursors, so the docs must say so.
Interval start, settled by measurement. The interval excludes parse and bind.
Production telemetry over six hours puts
parseat 0.050ms andbindat 0.032msmean; the PromQL is inlined in the doc. Against the 12ms worked example that is about
0.7%, at the edge of the Shell's 0.1ms display quantum.
Verification
No code, so no tests. What was verified instead:
file:linecitation resolves in-range, and each load-bearing one wascontent-checked against the tree.
cited line: that the value is in scope at each emission point, that each frame is
reached by the statements it claims to cover, and that cancellation, error, and
PortalSuspendedpaths correctly emit nothing.EmptyQuery,COPY ... FROM STDIN, and a replayedPortalState::Completedall bypasscommand_complete!, and a terminatingSUBSCRIBEreaches the read emissionpoint through
send_rows, which is why emission is guarded on statement type.not from inference.
The plan opens with an MVP that is not code and explicitly gates PR 1, since two of
its possible outcomes would change the design rather than confirm it.
Design review requested from @MaterializeInc/adapter. The Console and docs scopes are
affected by later PRs in the plan, not by this one.