diff --git a/satellite/metabase/stats.go b/satellite/metabase/stats.go index c7be0a871..a9a568bfc 100644 --- a/satellite/metabase/stats.go +++ b/satellite/metabase/stats.go @@ -5,9 +5,15 @@ package metabase import ( "context" + "database/sql" + "errors" "time" + + "storj.io/private/dbutil" ) +const statsUpToDateThreshold = 8 * time.Hour + // GetTableStats contains arguments necessary for getting table statistics. type GetTableStats struct { AsOfSystemInterval time.Duration @@ -22,6 +28,19 @@ type TableStats struct { func (db *DB) GetTableStats(ctx context.Context, opts GetTableStats) (result TableStats, err error) { defer mon.Task()(&ctx)(&err) + // if it's cockroach and statistics are up to date we will use them to get segments count + if db.impl == dbutil.Cockroach { + var created time.Time + err := db.db.QueryRowContext(ctx, `WITH stats AS (SHOW STATISTICS FOR TABLE segments) SELECT row_count, created FROM stats ORDER BY row_count DESC LIMIT 1`). + Scan(&result.SegmentCount, &created) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return TableStats{}, err + } + + if !created.IsZero() && statsUpToDateThreshold > time.Since(created) { + return result, nil + } + } err = db.db.QueryRowContext(ctx, `SELECT count(*) FROM segments `+db.impl.AsOfSystemInterval(opts.AsOfSystemInterval)).Scan(&result.SegmentCount) if err != nil { return TableStats{}, err diff --git a/satellite/metabase/stats_test.go b/satellite/metabase/stats_test.go index 4b9768e8f..189e8f2da 100644 --- a/satellite/metabase/stats_test.go +++ b/satellite/metabase/stats_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "storj.io/common/testcontext" "storj.io/private/dbutil" "storj.io/storj/satellite/metabase" @@ -83,6 +85,28 @@ func TestGetTableStats(t *testing.T) { }, }.Check(ctx, t, db) }) + + t.Run("use statistics", func(t *testing.T) { + defer metabasetest.DeleteAll{}.Check(ctx, t, db) + + obj1 := metabasetest.RandObjectStream() + metabasetest.CreateTestObject{}.Run(ctx, t, db, obj1, 4) + + _, err := db.UnderlyingTagSQL().ExecContext(ctx, "CREATE STATISTICS test FROM segments") + require.NoError(t, err) + + // add some segments after creating statistics to know that results are taken + // from statistics and not directly with SELECT count(*) + obj1 = metabasetest.RandObjectStream() + metabasetest.CreateTestObject{}.Run(ctx, t, db, obj1, 4) + + metabasetest.GetTableStats{ + Opts: metabase.GetTableStats{}, + Result: metabase.TableStats{ + SegmentCount: 4, + }, + }.Check(ctx, t, db) + }) } }) }