-
Notifications
You must be signed in to change notification settings - Fork 246
[Ideas] Add instrumentation and latency metrics to the Anser subsystem #1958
DescriptionAnser ( Today we can see what the filter did ( We know the wait is not negligible. A traced exchange on a 3-segment demo
The parts were sent within 1.3 ms of each other but folded ~7.2 ms apart — GoalMake every wait and every queueing delay in Anser visible in Definition of done
What to measureThree vantage points, each with its own clock. Keeping them separate is the
The pickup latency that dominates the trace above is between vantage points Note this issue does not ask for cluster-wide counters or a ImplementationThree changes, in this order. Each is independently reviewable and testable. Change 1 — per-node numbers in EXPLAIN
The trap you must handle. A field you add to the node state on a segment The supported escape hatch is the per-node extra text channel, which is how
Allocate Change 2 — the coordinator's sideThe QD merges in the backend running the query, so its numbers can ride out on
Accuracy caveats to document in the code. The consumer sleeps in slices Change 3 — build on the existing trace, don't duplicate it
Optional: exact pickup latencyThe 21 ms above can only be attributed exactly by comparing a segment's send
If that is more than you want to take on, skip it: reporting each side's own Testing
What I expect from code
|
All reactions
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.
All reactions
I've been working on something close to Change 1 (since July), but from the executor side: per-node wait timing for cross-slice blocking.
My original motivation was a bit different from performance analysis — I wanted a way to catch queries that get stuck (or fail outright) because of a cross-slice bug, where the optimizer and the executor disagree about slice assignment and producer/consumer locality is broken. That was the subject of my talk at Community Over Code Asia, "When the Optimizer Lies: Debugging Cross-Slice Execution in Apache Cloudberry": a Shared Scan over a CTE on a replicated table, hidden behind a scalar SubPlan, ends up hanging or failing with temporary file errors, and from the outside there is nothing to look at. Latency instrumentation turned out to be the practical way to make those cases visible — a consumer waiting forever on a producer that will never publish looks exactly like an unbounded wait on one node.
So I implemented it in the core as per-node wait statistics. In open-gpdb I added wait stats for cross-slice ShareInputScan (open-gpdb/gpdb#405): the consumer side measures how long it blocks waiting for the producer slice, and the elapsed time is reported in three ways — EXPLAIN ANALYZE (Cross-slice wait: N ms max (segK), M ms avg x P workers), the stats collector (cross_slice_wait_ms as a per-query aggregate), and the error context when a query is cancelled while blocked (e.g. by statement_timeout), which is what makes the pathological cases traceable after the fact.
It also makes wait skew across segments visible: the max/avg split per worker shows whether one segment is holding everything up or the wait is uniform.
The mechanism is generic enough that the same shape (per-node accumulator → max/avg per segment → EXPLAIN VERBOSE + a query-level aggregate) should fit the Anser producer/consumer waits directly. If the community is interested, I'd be happy to port it to Cloudberry — either as a general per-node wait-timing facility that Change 1 can build on, or just as input for whoever picks up Change 1. I can prepare pull request to cloudberry if the community is interested in it.
All reactions
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.
Thank you! I've updated description to reflect actual changes in a code. In #1942 we found out that runtime data could be sent in existing QD<->QE session, that's awesome, save us a lot of CPU/time ) Why I created issue while original PR wasn't merged - because I'm in context right now фтв have all the necessary information at hand.
All reactions
I've been working on something close to Change 1 (since July), but from the executor side: per-node wait timing for cross-slice blocking.
My original motivation was a bit different from performance analysis — I wanted a way to catch queries that get stuck (or fail outright) because of a cross-slice bug, where the optimizer and the executor disagree about slice assignment and producer/consumer locality is broken. That was the subject of my talk at Community Over Code Asia, "When the Optimizer Lies: Debugging Cross-Slice Execution in Apache Cloudberry": a Shared Scan over a CTE on a replicated table, hidden behind a scalar SubPlan, ends up hanging or failing with temporary file errors, and from the outside there is nothing to look at. Latency instrumentation turned out to be the practical way to make those cases visible — a consumer waiting forever on a producer that will never publish looks exactly like an unbounded wait on one node.
So I implemented it in the core as per-node wait statistics. In open-gpdb I added wait stats for cross-slice ShareInputScan (open-gpdb/gpdb#405): the consumer side measures how long it blocks waiting for the producer slice, and the elapsed time is reported in three ways — EXPLAIN ANALYZE (Cross-slice wait: N ms max (segK), M ms avg x P workers), the stats collector (cross_slice_wait_ms as a per-query aggregate), and the error context when a query is cancelled while blocked (e.g. by statement_timeout), which is what makes the pathological cases traceable after the fact.
It also makes wait skew across segments visible: the max/avg split per worker shows whether one segment is holding everything up or the wait is uniform.
The mechanism is generic enough that the same shape (per-node accumulator → max/avg per segment → EXPLAIN VERBOSE + a query-level aggregate) should fit the Anser producer/consumer waits directly. If the community is interested, I'd be happy to port it to Cloudberry — either as a general per-node wait-timing facility that Change 1 can build on, or just as input for whoever picks up Change 1. I can prepare pull request to cloudberry if the community is interested in it.
Thank you, it will be quite usefull. I do not think it should be part of Anser - it's more general data, needed to DBA regardless of whether Anser is enabled or not. You should definitely finish your work and let collect this statistics.
All reactions
For the anser.stats() / stats_reset() contract, could each snapshot identify its reset generation, and could the API document how a reader knows a reset has completed? A consumer computing interval rates from cumulative counters needs to distinguish two reads that straddle a reset. Checking for a negative delta alone can miss the reset if enough new activity has already exceeded the previous count.
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.
All reactions
For the
anser.stats()/stats_reset()contract, could each snapshot identify its reset generation, and could the API document how a reader knows a reset has completed? A consumer computing interval rates from cumulative counters needs to distinguish two reads that straddle a reset. Checking for a negative delta alone can miss the reset if enough new activity has already exceeded the previous count.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.
Yes, thank you. It is quite important. If we want to add a stat() method, there should be a `stats_reset()' method and the time when the reset was performed. I remember a multi-year discussion among pg-hackers about adding a reset time to pg_stat_statements. Luckily, it finally got merged )
All reactions
The examples of data on my dev demo cluster
- How to trace -
set anser.debug=on; - Example of raw data from logs
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.527686 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: producer init cond=0 part=0/1 elems=3334 payload=67108928 state=ok",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.551469 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD part cond=0 from seg2 (says part 2 of 3) 1/3 bytes=1048592 -> collecting",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.558633 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD part cond=0 from seg0 (says part 0 of 3) 2/3 bytes=1048592 -> collecting",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.558684 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD subscribe cond=0 from seg0 (channel still collecting)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.558712 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD subscribe cond=0 from seg1 (channel still collecting)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.558737 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD subscribe cond=0 from seg2 (channel still collecting)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.565990 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD part cond=0 from seg1 (says part 1 of 3) 3/3 bytes=1048592 -> complete",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.566018 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD delivering cond=0 to 3 subscriber(s)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.566477 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD pushed cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.566860 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD pushed cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/qddir/demoDataDir-1/log/gpdb-2026年09月07日_112618.csv:2026年09月07日 11:27:19.567277 UTC,"xifos","postgres",p88346,th706219584,"[local]",,2026年09月07日 11:26:32 UTC,0,con23,cmd6,seg-1,,,,sx1,"LOG","00000","anser: QD pushed cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast1/demoDataDir0/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.534745 UTC,"xifos","postgres",p88524,th1725312576,"127.0.0.1","56156",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg0,slice2,,,sx1,"LOG","00000","anser: producer init cond=0 part=0/3 elems=3334 payload=67108928 state=ok",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast1/demoDataDir0/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.536508 UTC,"xifos","postgres",p88524,th1725312576,"127.0.0.1","56156",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg0,slice2,,,sx1,"LOG","00000","anser: producer child exhausted, publishing (state=ok)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast1/demoDataDir0/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.543574 UTC,"xifos","postgres",p88524,th1725312576,"127.0.0.1","56156",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg0,slice2,,,sx1,"LOG","00000","anser: seg0 published cond=0 part=0/3 bytes=1048592 cancelled=0 sent=1",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast1/demoDataDir0/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.544858 UTC,"xifos","postgres",p88378,th1725312576,"127.0.0.1","59664",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg0,slice1,,,sx1,"LOG","00000","anser: seg0 subscribed cond=0, waiting up to 1000 ms",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast1/demoDataDir0/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.567852 UTC,"xifos","postgres",p88378,th1725312576,"127.0.0.1","59664",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg0,slice1,,,sx1,"LOG","00000","anser: seg0 received cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast2/demoDataDir1/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.534632 UTC,"xifos","postgres",p88525,th867016256,"127.0.0.1","33560",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg1,slice2,,,sx1,"LOG","00000","anser: producer init cond=0 part=1/3 elems=3334 payload=67108928 state=ok",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast2/demoDataDir1/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.538237 UTC,"xifos","postgres",p88525,th867016256,"127.0.0.1","33560",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg1,slice2,,,sx1,"LOG","00000","anser: producer child exhausted, publishing (state=ok)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast2/demoDataDir1/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.544624 UTC,"xifos","postgres",p88525,th867016256,"127.0.0.1","33560",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg1,slice2,,,sx1,"LOG","00000","anser: seg1 published cond=0 part=1/3 bytes=1048592 cancelled=0 sent=1",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast2/demoDataDir1/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.544876 UTC,"xifos","postgres",p88380,th867016256,"127.0.0.1","55068",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg1,slice1,,,sx1,"LOG","00000","anser: seg1 subscribed cond=0, waiting up to 1000 ms",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast2/demoDataDir1/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.568240 UTC,"xifos","postgres",p88380,th867016256,"127.0.0.1","55068",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg1,slice1,,,sx1,"LOG","00000","anser: seg1 received cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast3/demoDataDir2/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.534629 UTC,"xifos","postgres",p88526,th72871488,"127.0.0.1","47130",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg2,slice2,,,sx1,"LOG","00000","anser: producer init cond=0 part=2/3 elems=3334 payload=67108928 state=ok",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast3/demoDataDir2/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.536992 UTC,"xifos","postgres",p88526,th72871488,"127.0.0.1","47130",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg2,slice2,,,sx1,"LOG","00000","anser: producer child exhausted, publishing (state=ok)",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast3/demoDataDir2/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.543316 UTC,"xifos","postgres",p88526,th72871488,"127.0.0.1","47130",2026年09月07日 11:27:19 UTC,0,con23,cmd6,seg2,slice2,,,sx1,"LOG","00000","anser: seg2 published cond=0 part=2/3 bytes=1048592 cancelled=0 sent=1",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast3/demoDataDir2/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.544872 UTC,"xifos","postgres",p88379,th72871488,"127.0.0.1","46024",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg2,slice1,,,sx1,"LOG","00000","anser: seg2 subscribed cond=0, waiting up to 1000 ms",,,,,,"explain analyze select
/home/xifos/git/cloudberry/gpAux/gpdemo/datadirs/dbfast3/demoDataDir2/log/gpdb-2026年09月07日_112617.csv:2026年09月07日 11:27:19.568601 UTC,"xifos","postgres",p88379,th72871488,"127.0.0.1","46024",2026年09月07日 11:26:40 UTC,0,con23,cmd6,seg2,slice1,,,sx1,"LOG","00000","anser: seg2 received cond=0 bytes=1048592 cancelled=0",,,,,,"explain analyze select
- The conslusion based on debug data
Three-way events show the spread across segments:
┌─────────────┬────────────────────────────────────────────────────┐
│ t (ms) │ Event │
├─────────────┼────────────────────────────────────────────────────┤
│ 0.0 │ 3 producers init — part=0/3, 1/3, 2/3 (spread 0.1) │
├─────────────┼────────────────────────────────────────────────────┤
│ 1.9 – 3.6 │ children exhausted, publishing (seg0, seg2, seg1) │
├─────────────┼────────────────────────────────────────────────────┤
│ 8.7 – 10.0 │ all 3 published, sent=1 (seg2, seg0, seg1) │
├─────────────┼────────────────────────────────────────────────────┤
│ 10.2 – 10.2 │ 3 consumers subscribe (spread 0.02) │
├─────────────┼────────────────────────────────────────────────────┤
│ 16.8 │ QD folds seg2 → 1/3 collecting │
├─────────────┼────────────────────────────────────────────────────┤
│ 24.0 │ QD folds seg0 → 2/3 collecting │
├─────────────┼────────────────────────────────────────────────────┤
│ 24.1 │ QD receives the 3 subscribes (spread 0.05) │
├─────────────┼────────────────────────────────────────────────────┤
│ 31.4 │ QD folds seg1 → 3/3 complete │
├─────────────┼────────────────────────────────────────────────────┤
│ 31.4 │ QD delivering to 3 subscribers │
├─────────────┼────────────────────────────────────────────────────┤
│ 31.8 – 32.6 │ 3 pushes, 1048592 bytes each │
├─────────────┼────────────────────────────────────────────────────┤
│ 33.2 – 34.0 │ all 3 consumers received │
└─────────────┴────────────────────────────────────────────────────┘
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