Repository navigation
Conversation
6595bf9 to
daf2762
Compare
eduardo-landin-mestre
left a comment
There was a problem hiding this comment.
🤖 stmt.schema looks good: allowlisted metrics, parameterized schema filters, the float64 scan for large SUMs, and clear docs on the digest catch-all undercount.
Requesting changes for one issue in wait.io.table: with group-by: both, schema series share metric names with table series and differ only by the missing tbl tag, so Datadog/SignalFx queries filtered by db double count (details inline). The rest are smaller.
I checked the rollup SQL (weighted avg via SUM(sum_timer_x)/SUM(count_x), count_star mapping for *_wait, MIN ignoring idle tables) and it looks correct. Unit tests and go vet pass locally; I did not run the MySQL integration tests.
Nits:
stmt.schemais not listed indocs/content/about/production-readiness.md(presumably "New").
| for i := 2; i < len(cols); i++ { | ||
| group := map[string]string{"db": dbName, "tbl": tblName} | ||
| if schema { | ||
| group = map[string]string{"db": dbName} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
🤖 👨 Agreed. We removed both. Each collection now emits either table series or schema series under these metric names, so switching to schema grouping cannot add a second set to a db aggregation. Table grouping remains the default.
| } | ||
|
|
||
| o.query, o.params = TableIoWaitQuery(dom.Options, dom.Metrics) | ||
| o.groupBy = dom.Options[OPT_GROUP_BY] |
There was a problem hiding this comment.
🤖 Unknown values (e.g. a typo like schemas) silently fall back to table output. Could Prepare return an error for anything other than table, schema, both, or empty, like it does for an invalid truncate-timeout?
There was a problem hiding this comment.
🤖 👨 Prepare now rejects values other than empty, table, or schema, including the removed both value. The collector help lists only table and schema.
|
|
||
| rows, err := t.db.QueryContext(ctx, o.query, o.params...) | ||
| var metrics []blip.MetricValue | ||
| if o.groupBy != "schema" { |
There was a problem hiding this comment.
🤖 In both mode the two SELECTs are separate reads, so events arriving between them land in one series but not the other for that interval, and with the default truncate-table: yes the existing window before TRUNCATE (where events are lost) gets longer. One option is to roll up schema values in Go from the table rows when the needed count_*/sum_timer_* columns are selected. Otherwise it may be worth a sentence in the docs that table and schema totals can disagree slightly.
There was a problem hiding this comment.
🤖 👨 Removing both also removes the second read. A collection now runs one SELECT for its chosen grouping before the optional truncate, so the two series can no longer disagree within a collection.
| metrics = append(metrics, values...) | ||
| } | ||
|
|
||
| if o.truncate { |
There was a problem hiding this comment.
🤖 Nit: the move dropped the comments explaining why lock_wait_timeout is set first and why TruncateError must run even when there is no error. Worth keeping them.
There was a problem hiding this comment.
🤖 👨 Restored comments explaining why we set lock_wait_timeout before truncating and why TruncateError processes the metrics even when truncation succeeds.
| }, | ||
| }, | ||
| Groups: []blip.CollectorKeyValue{ | ||
| {Key: "db", Value: "the database name for the corresponding table io, or empty string for all dbs"}, |
There was a problem hiding this comment.
🤖 Nit: the db/tbl group help still says "or empty string for all dbs" and does not mention schema series, which have only db.
There was a problem hiding this comment.
🤖 👨 Updated the group descriptions: db identifies the database in either mode, and tbl is present only for table series.
| func TestCollectMySQL80(t *testing.T) { | ||
| _, db, err := test.Connection("mysql80") | ||
| if err != nil { | ||
| t.Skip("mysql80 not running") |
There was a problem hiding this comment.
🤖 Nit: this skips on any connection error, while the new wait.io.table tests skip only on *net.OpError and fail otherwise. Using the same pattern here would keep config or auth errors from hiding as skips.
There was a problem hiding this comment.
🤖 👨 The stmt.schema test now skips only for a network connection error. Configuration and authentication errors fail the test.
| for i := 2; i < len(cols); i++ { | ||
| group := map[string]string{"db": dbName, "tbl": tblName} | ||
| if schema { | ||
| group = map[string]string{"db": dbName} |
There was a problem hiding this comment.
🤖 Non-blocking: with group-by: both, the schema series and the table series use the same metric name (e.g. wait.io.table.count_star). The only difference is that the schema series has no tbl tag. In Datadog or Prometheus, aggregating by db (e.g. sum by (db), or a Datadog sum filtered to db:app) adds up every table series and the schema series, so the total is 2x. Every dashboard would have to filter on whether tbl is present to avoid that.
Also, the existing Help().Groups text says tbl is "empty string for all tables", but these series leave tbl out entirely.
Suggestions: give schema rollups their own metric names (e.g. a schema_ prefix or a separate domain), or at least document the double-counting for both in docs/content/metrics/domains/wait.io.table/_index.md.
There was a problem hiding this comment.
🤖 👨 Agreed. We removed both, so the collector no longer emits table and schema series with the same name in one collection. I also corrected the help text to say that schema series omit tbl.
| for _, column := range columns { | ||
| switch { | ||
| case strings.HasPrefix(column, "count_"), strings.HasPrefix(column, "sum_timer_"): | ||
| selectColumns = append(selectColumns, fmt.Sprintf("SUM(%s) AS %s", column, column)) |
There was a problem hiding this comment.
🤖 Non-blocking: with truncate-table: no the metric type is CUMULATIVE_COUNTER, but a schema-level SUM across tables only stays monotonic if no table rows leave table_io_waits_summary_by_table. They do leave in normal operation: DROP TABLE removes the row, which happens at the cut-over of every gh-ost/pt-osc migration (the old table is dropped), and so does eviction under performance_schema_max_table_instances. When that happens the schema sum drops. sink/delta.go:99-103 then sends the whole cumulative value as the delta, so you get one large spike per migration.
Table-level series do not have this problem because the dropped table series simply stops. The default truncate mode is not affected either. Consider documenting this limitation for group-by: schema|both with truncate-table: no, or recommending the default truncate mode for schema rollups.
There was a problem hiding this comment.
🤖 👨 This remains a real limitation. Dropping a table can lower a cumulative schema total and cause a delta spike. I added a prominent warning beside group-by: schema and called it out in the PR Risk section, with a recommendation to use the default truncate-table: yes. The documentation does not eliminate the behavior.
zyin-squareup
left a comment
There was a problem hiding this comment.
🤖 This review was generated by an AI agent on behalf of @zyin-squareup.
Verdict: approve. I found no blocking issues, and two non-blocking items are in the inline comments.
Both new modes are opt-in, and the default wait.io.table path still does the same thing. It runs the same query and scans into int64, then applies truncation and TruncateErrorPolicy to the collected metrics as before. One small behavior change, and I think it is an improvement: collectQuery now returns rows.Err(), so an iteration error is reported instead of being dropped and then truncating anyway.
Non-blocking
- With
group-by: both, aggregating bydbcounts I/O twice, because the table and schema series share metric names (inline onmetrics/wait.io.table/table.go:332). - With
truncate-table: no, the schema rollups go backwards whenever a table is dropped (e.g. at a gh-ost/pt-osc cut-over), and the delta sink then reports the whole cumulative value as a spike (inline onmetrics/wait.io.table/query.go:32). - Nit: moving the truncate block into
Collectremoved the comments that explained whylock_wait_timeoutis set first and whyTruncateErrorruns even when there is no error. It would be good to keep them.
What I checked and found sound
countSuffixmaps*_timer_waittocount_starand every other*_timer_<op>to an existingcount_<op>column.- The weighted average uses
NULLIF/COALESCE, so it cannot divide by zero, and the minimum ignores tables with no events. - Schema
SUMvalues come back asDECIMALand are scanned intofloat64, so values aboveMaxInt64do not overflow. group-byvalues are validated against the optionValuesin Help (collector.go:92), so a typo is rejected instead of silently falling back totable.stmt.schemachecks metric names against an allowlist before interpolating them into SQL, and schema names are passed as bound parameters. The null-schema/digest catch-all row is excluded, and the docs say so.
Validation: At this commit, go build ./..., go vet, and go test -count=1 ./metrics/stmt.schema/ ./metrics/wait.io.table/ ./metrics/ all pass locally. The three MySQL 8.0 integration tests were skipped because no local MySQL was running, so I am relying on the PR description and green CI for the live-DB paths.
Why
Blip reports table I/O by table and query counters at server scope. Operators who share one MySQL server cannot compare statement load and table activity by schema without building their own rollups.
What
Add an opt-in
stmt.schemadomain for statement counts, latency, errors, and row activity grouped bydb.Add
group-by: table|schematowait.io.table; the current table output remains the default.How
The new collector sums selected counters from
events_statements_summary_by_digestbySCHEMA_NAME. The I/O collector reads either table series or schema rollups, never both in one collection. Schema rollups weight averages by event count and exclude empty tables from minimum wait times. Schema sums scan asfloat64because MySQL can return values aboveMaxInt64.Risk
The new domain and schema grouping are opt-in. Schema statement series omit null-schema and digest catch-all rows, so they can undercount when the digest table fills. If a table is dropped while schema grouping uses
truncate-table: no, the cumulative schema total can fall and cause a delta spike; the default truncation mode avoids this.Testing
Ran
go test -count=1 ./...with a local MySQL 8.0.34 container. The MySQL integration tests for both collectors and the schema overflow regression passed. Rango veton the changed packages.Generated with Codex