[Ideas] Add instrumentation and latency metrics to the Anser subsystem #1958
Replies: 3 comments 4 replies
|
Hi, this looks interesting. I'd like to work on this. I'm thinking of starting with Change 1 — per-node wait time in EXPLAIN as the first step, including the producer/consumer wait instrumentation and the segment-side extra text handling. Is anyone already working on this part? If not, I'd be happy to take it and submit a PR for Change 1 first. |
|
For the This also affects interval histogram quantiles: subtract corresponding bucket counts only across compatible snapshots from one reset generation, then derive an approximate quantile from that interval distribution; subtracting the displayed p99 values would not give an interval p99. Given the no-new-locks constraint, stating whether count, total time and bucket reads are a best-effort snapshot would help consumers avoid asserting exact consistency during concurrent updates. This is feedback on the proposed API, not a reproduced implementation issue. Disclosure: I build Telemetry; this reply was drafted with AI assistance. |
|
The examples of data on my dev demo cluster
34.0 ms first-init to last-received; 25.3 ms publish to received. One thing that jumps out now that it's relative: the three parts were sent within 1.3 ms of each other (8.7 → 10.0) but folded 16.8 → 31.4, evenly spaced about 7.2 ms apart. So roughly 21 ms of the 34 is the coordinator picking parts up one at a time, not doing work — a 1 MB fold is ~0.1 ms and a 1.4 MB base64 decode ~1–2 ms. That points at the drain cadence: one part per processResults sweep, gated by the interconnect wait loop rather than by anything Anser does. What I want - could gather the similar info without enabling debug mode and processing raw debug info |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Description
Anser (
gpcontrib/anser/) is a runtime pub/sub facility: producer nodes on thesegments publish a bloom filter over a join-build key, the coordinator unions
the parts, and consumer nodes on the segments receive it and prune probe rows.
Everything travels over the dispatch connection the coordinator already holds
open to each segment — there is no shared memory and no background worker. See
gpcontrib/anser/README.mdfor the architecture before starting.Today we can see what the filter did (
Rows Removed by Bloom FilterinEXPLAIN ANALYZE) but nothing about what it cost. A consumer blocks until thecoordinator delivers the merged filter; if that wait is expensive the filter is
a net loss, and we currently have no way to tell from a plan.
We know the wait is not negligible. A traced exchange on a 3-segment demo
cluster with a 1 MB filter took 34 ms end to end, and only ~13 ms of that
was work:
sent=1)The parts were sent within 1.3 ms of each other but folded ~7.2 ms apart —
so roughly 21 ms, 60% of the exchange, is the coordinator picking parts up one
at a time, not doing work (a 1 MB fold is ~0.1 ms, a 1.4 MB base64 decode
~1–2 ms). That pickup latency is gated by the interconnect wait loop, it scales
with segment count, and nothing in
EXPLAINshows it. Attributing it is themain reason this issue exists.
Goal
Make every wait and every queueing delay in Anser visible in
EXPLAIN, on bothsides of the exchange, so a filter that costs more than it saves can be
identified from a plan alone.
Definition of done
EXPLAIN (ANALYZE, VERBOSE)shows producer publish cost and consumer waittime, including for segment-executed nodes.
completion, delivery, and per-part fold time.
README.mdgains a "Metrics" section: every field, its unit, and where itis measured.
src/) is untouched.What to measure
Three vantage points, each with its own clock. Keeping them separate is the
single most important thing to get right — do not subtract a timestamp taken on
a segment from one taken on the coordinator.
AnserProducePublishPart()(src/anserbloomproduce.c:60),AnserSidebandPublish()(src/ansersideband.c:94)ExecAnserBloomFilterConsumeSideband()(src/anserbloomconsume.c:100),AnserSidebandConsumeWait()(src/ansersideband.c:137)anser_disp_apply_part()(src/anserdispatch.c:302),anser_disp_deliver()(:372),anser_disp_push()(:394)The pickup latency that dominates the trace above is between vantage points
(segment send → coordinator fold). Deriving it exactly needs a send timestamp on
the wire, which means comparable clocks — see "Optional: exact pickup latency".
Note this issue does not ask for cluster-wide counters or a
anser.stats()view. The subsystem has no shared memory any more, and itcreates no catalog objects (
CREATE EXTENSIONis not part of installing it), sothere is nowhere for cross-backend totals to live and no SQL surface to expose
them. Per-query numbers in
EXPLAINare the deliverable. If cross-queryaggregation is wanted later it needs its own design discussion — reintroducing
either shared memory or an extension is a bigger decision than instrumentation.
Implementation
Three changes, in this order. Each is independently reviewable and testable.
Change 1 — per-node numbers in EXPLAIN
node. Record per node: number of waits, total wait time, longest
single wait, and for the consumer the outcome.
AnserProducePublishPart(); the accumulators belong inAnserBloomProduceScanState(src/anserplanexec.c:80), and the publishis triggered from
anser_produce_next()(:356) when the child isexhausted.
ExecAnserBloomFilterConsumeSideband(); accumulators inAnserBloomConsumeScanState(src/anserplanexec.c:94), driven fromanser_consume_receive()(:498).anser_produce_explain()(src/anserplanexec.c:435) andanser_consume_explain()(:648), gated ones->analyze && es->verbose.Use
ExplainPropertyInteger/ExplainPropertyFloatso JSON/YAML/XMLoutput works for free — never
appendStringInfointo the plan text.The trap you must handle. A field you add to the node state on a segment
does not reach the QD. Read the comment at
src/anserplanexec.cabove theInstrCountFiltered1/2calls in the consumer's exec loop: the existing codeuses those counters deliberately, because only the fixed fields of
CdbExplain_StatInst(src/backend/commands/explain_gp.c:44) travel back. Yournew timers are not in that struct.
The supported escape hatch is the per-node extra text channel, which is how
HashreportsExtra Text: (seg2) Hash chain length ...:PlanState.cdbexplainbufandPlanState.cdbexplainfun(
src/include/nodes/execnodes.h:1152-1153)cdbexplain_collectExtraText()(
src/backend/commands/explain_gp.c:1308) and shipped to the QDsrc/backend/executor/nodeRuntimeFilter.c:174-177—the closest existing analogue, a runtime-filter node doing exactly this
Allocate
cdbexplainbufin*_beginwhenestate->es_instrumentis set,install a
cdbexplainfunthat appends your numbers, and the QD shows them asExtra Textper segment. No core change is needed — if you find yourselfediting anything under
src/, stop and re-read this paragraph.Change 2 — the coordinator's side
The QD merges in the backend running the query, so its numbers can ride out on
the plan rather than into a stats table.
AnserDispChannel(src/anserdispatch.c):first-part arrival, completion, delivery, accumulated fold time, and part
count. All from one clock, all in memory already owned by the query.
EXPLAINoutput — the QD is wherethat node's plan output is assembled, so no transport is needed. One line
of
key=valuepairs, matching the shape of theExtra Textlines.instr_timethroughout (src/include/portability/instr_time.h:INSTR_TIME_SET_CURRENT,INSTR_TIME_SUBTRACT,INSTR_TIME_GET_MICROSEC). Do not useGetCurrentTimestamp()fordurations.
Accuracy caveats to document in the code. The consumer sleeps in slices
(
ANSER_SIDEBAND_POLL_MS= 100 ms insrc/ansersideband.c), so measure walltime across the whole wait call, never by counting loop iterations, and note
that sub-poll-interval waits are quantized. On the coordinator side, note that
fold time and pickup latency are different things and the fold is the small one.
Change 3 — build on the existing trace, don't duplicate it
anser.debug(ANSER_DEBUG(),include/anser.h:78) already logs every step ofthe exchange with the sender's identity. Reuse its call sites rather than adding
a parallel set:
instead of adding a second line.
key=valuepairs — it is grep-and-awkmaterial and people already have scripts.
update it in the same commit.
Optional: exact pickup latency
The 21 ms above can only be attributed exactly by comparing a segment's send
time with the coordinator's fold time. If you want that:
anser1 ...ininclude/ansersideband.h) — there is room, and the parser takes fieldspositionally, so bump
ANSER_WIRE_TAGif you change the layout.meaningless without synchronised clocks across hosts. Do not fold it
into any other total.
If that is more than you want to take on, skip it: reporting each side's own
durations still narrows the gap to "time spent between publish and fold", which
is the actionable finding.
Testing
gpcontrib/anser/sql/anser_test.sql(+expected/anser_test.out).randomly. Assert properties instead: a counter is
> 0, a wait totalincreased after a known round trip, an outcome field says
delivered.Return booleans from C test helpers (
src/anser_test.c) rather thanprinting numbers.
EXPLAINpart, prefer asserting on plan shape — a rawEXPLAIN (ANALYZE, VERBOSE)in expected output is unstable. If you must,filter it through a query that only checks the property is present.
make -C gpcontrib/anser install && make -C gpcontrib/anser installcheck(the
installchecktarget arms the cluster itself). Requiresshared_preload_libraries='anser'andanser.enable=on; noCREATE EXTENSIONis needed for the subsystem itself.What I expect from code
EXPLAIN (ANALYZE, VERBOSE)— the normal caseThree segments,
anser_rf_build(200 rows) joined toanser_rf_probe(2000 rows), the filter working as intended:
Read this carefully — it encodes several requirements:
report
Anser Publish Time(encode + send), not a wait count. Anythinglabelled "wait" on the producer is a leftover from the old libpq transport,
where it blocked for an acknowledgement.
Anser Coordinatoris the line that makes the trace above legible:first/complete/deliveredare offsets on the QD clock,foldis realwork. In this example
complete - first = 14.5msforfold=0.31ms, whichsays the cost is pickup, not merging.
Anser Resultmust distinguishdelivered/cancelled/timeout.Without it, a fast failure and a successful delivery look identical.
reads=on the consumer — messages taken off the socket while waiting.reads=0on a timeout means nothing ever arrived; a non-zero count meanssomething arrived that was not ours, which is a different bug.
Extra Textline is the per-segment breakdown shipped throughcdbexplainbuf. The summary properties above it are the winning segment'svalues, like every other per-node MPP statistic. One line,
key=value, nowrapping.
child is exhausted, so its cost shows in the node's last tuple time
(
0.031..10.002). The consumer must receive before returning anything, so itswait shows in the first tuple time (
31.902..). Do not "fix" this; it isthe truth about when each side blocks.
positives. The filter may pass rows that do not join — it must never reject
one that does.
EXPLAIN (ANALYZE)without VERBOSE — unchangedThe new lines are VERBOSE-only. Plain
ANALYZEkeeps exactly today's output:EXPLAIN (ANALYZE, VERBOSE)— the case this feature exists forA producer never published (squelched, or a segment whose slice was abandoned),
so the channel never completed and the consumer waited
anser.timeout_msfornothing before failing open. The query is still correct — just slower than with
the feature off, which is precisely what we cannot see today:
rows=667— every probe row passed, no pruning happened, and a second was spentwaiting. Note
parts=2with no completion: the coordinator's line shows exactlywhy it timed out, which is the diagnosis this whole issue is for.
Use case/motivation
No response
Related issues
#1942
Are you willing to submit a PR?
All reactions