storage: estimate MySQL snapshot row counts for large tables - #37907
storage: estimate MySQL snapshot row counts for large tables#37907peterdukelarsen wants to merge 1 commit into
Conversation
Mirror the Postgres source's approach to the snapshot size gauge: ask the optimizer for a row estimate first and only run an exact COUNT(*) when the estimate is at or below a threshold. Large tables report the information_schema.tables estimate directly, skipping an O(rows) index walk during snapshot setup. A NULL or zero estimate falls through to the exact count. The threshold is the new mysql_source_snapshot_exact_count_max_rows dyncfg, defaulting to 1M rows to match the Postgres source's cutoff. Setting it to 0 forces the estimated path everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Ran a local test to confirm what happens if no stats have been calculated. Looks like we'll get a row reporting 0 rows. |
ublubu
left a comment
There was a problem hiding this comment.
At a glance, the logic is reasonable.
But I'm having trouble understanding the comments.
| # 0 forces the estimated-size path for every table, the default forces | ||
| # the exact COUNT(*) path for workload-sized tables. |
There was a problem hiding this comment.
Is this comment saying that parallel_workload's tables all have <1M records?
The code here doesn't control either workload size or the default flag value.
There was a problem hiding this comment.
They are going to be way smaller than 1 million records. My experience with parallel workload was that everything has to be extremely small, otherwise Materialize deadlocks quickly. There are a bunch of old issues open for that, but we can retry/revisit.
| /// The largest optimizer-estimated table size for which the MySQL snapshot size | ||
| /// gauge is computed with an exact `COUNT(*)`. Larger tables report the | ||
| /// `information_schema` estimate directly, skipping the O(rows) count. |
There was a problem hiding this comment.
| /// The largest optimizer-estimated table size for which the MySQL snapshot size | |
| /// gauge is computed with an exact `COUNT(*)`. Larger tables report the | |
| /// `information_schema` estimate directly, skipping the O(rows) count. | |
| /// If the optimizer estimates the table has fewer rows than this, compute the exact row count with `COUNT(*)`. | |
| /// Otherwise, report the `information_schema` estimate directly. |
| /// is O(total). Worker count is small, so the OFFSET scans dominate. `total` is the row | ||
| /// count supplied by the caller. It can be an optimizer estimate for large tables, so | ||
| /// the partitions are approximate. An overestimate walks off the end of the index and | ||
| /// stops with fewer boundaries, an underestimate leaves a larger final partition, both | ||
| /// still correctly partition the table. Returns None if the primary key column type is | ||
| /// not supported or the table is too small to split. |
There was a problem hiding this comment.
I'm struggling with this comment.
In particular, I'm not sure about:
- what the OFFSET scans are dominating
- "this" in "so this is O(total)"
- "walks off the end of the index and stops with fewer boundaries" (Also, that's a very long sentence.)
Nit: total is the row count supplied by the caller
| /// For every table, read the row count (exact only for small tables) and, for a | ||
| /// supported single-column primary key, compute the PK-range split boundaries, | ||
| /// concurrently over at most `worker_count` connections. `None` bounds means | ||
| /// single-worker fallback for that table. The counts are reused for both the sampling | ||
| /// stride and the snapshot size gauge. |
There was a problem hiding this comment.
This mostly reads clearly, but I can't tell what "snapshot size gauge" is. "Sampling stride" feels better, but I'm still not confident what it means either.
| .query_first(format!("SELECT COUNT(*) FROM {}", table)) | ||
| // The optimizer's row estimate for the table. InnoDB keeps it roughly current | ||
| // (within the churn since the last stats recalculation), but it can be NULL or | ||
| // stale-at-zero, in which case we fall through to the exact count. |
Motivation
Part of SS-360.
Count(*) can be very slow and we can speed it up with using table size statistics.
Description
If size estimate is > 1M just use the size estimate. Otherwise still use count(*).
This broadly matches how Postgres handles grabbing/reporting the full row-count and is inspired by that. Row count is used for Reporting progress in the frontend and if we roll out parallel snapshotting for partitioning data. Accuracy is not critical for correctness.