Skip to content

Commit

Permalink
replicators: Use a row estimate for snapshotting progress
Browse files Browse the repository at this point in the history
Especially for very large tables, doing a full count of the number of
rows using `COUNT(*)` can be quite expensive - we've seen issues in
real-world deployment scenarios where this has taken quite a long
time (long enough that we didn't wait to let it finish, instead opting
to just kill the query backend). A much faster, albeit less accurate way
of *estimating* the number of rows in the table is to just look at the
`reltuples` column in the `pg_class` entry for the table. Since we're
only using this to report snapshotting progress, we don't need a
100%-guaranteed-accurate count of the rows, so this should be perfectly
fine to do here, and save a lot of time and load on the upstream db
during snapshotting.

Fixes: ENG-2827
Change-Id: I72ee9e5f54597e30d83f2f9221163237086c600a
Reviewed-on: https://gerrit.readyset.name/c/readyset/+/4873
Tested-by: Buildkite CI
Reviewed-by: Dan Wilbanks <dan@readyset.io>
  • Loading branch information
glittershark committed May 9, 2023
1 parent b8c88d9 commit 9f85776
Showing 1 changed file with 17 additions and 13 deletions.
30 changes: 17 additions & 13 deletions replicators/src/postgres_connector/snapshot.rs
Expand Up @@ -466,18 +466,19 @@ impl TableDescription {
) -> ReadySetResult<()> {
let mut cnt = 0;

let nrows = transaction
let approximate_rows = transaction
.query_one(
format!(
"SELECT count(*) AS nrows FROM \"{}\".\"{}\"",
self.schema()?,
&self.name.name,
)
.as_str(),
&[],
// Fetch an *approximate estimate* of the number of rows in the table, rather than
// an exact count (the latter is *significantly* more expensive, especially for
// large tables). We're only using this for reporting snapshotting progress, so an
// approximate row count should be fine
"SELECT c.reltuples::bigint AS approximate_nrows
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.relname = $1 AND n.nspname = $2",
&[&self.name.name.as_str(), &self.schema()?.as_str()],
)
.await?
.try_get::<_, i64>("nrows")?;
.try_get::<_, i64>("approximate_nrows")?;

// The most efficient way to copy an entire table is COPY BINARY
let query = format!(
Expand All @@ -494,7 +495,7 @@ impl TableDescription {

pin_mut!(binary_row_batches);

info!(rows = %nrows, "Snapshotting started");
info!(%approximate_rows, "Snapshotting started");
let progress_percentage_metric: metrics::Gauge = register_gauge!(
recorded::REPLICATOR_SNAPSHOT_PERCENT,
"schema" => self.schema()?.to_string(),
Expand Down Expand Up @@ -568,9 +569,12 @@ impl TableDescription {
&& last_report_time.elapsed().as_secs() > snapshot_report_interval_secs
{
last_report_time = Instant::now();
let estimate =
crate::estimate_remaining_time(start_time.elapsed(), cnt as f64, nrows as f64);
let progress_percent = (cnt as f64 / nrows as f64) * 100.;
let estimate = crate::estimate_remaining_time(
start_time.elapsed(),
cnt as f64,
approximate_rows as f64,
);
let progress_percent = (cnt as f64 / approximate_rows as f64) * 100.;
let progress = format!("{:.2}%", progress_percent);
info!(rows_replicated = %cnt, %progress, %estimate, "Snapshotting progress");
progress_percentage_metric.set(progress_percent);
Expand Down

0 comments on commit 9f85776

Please sign in to comment.