From a6d3b94a2e612b66592ec9642b4b4f5bfdbca1f8 Mon Sep 17 00:00:00 2001 From: Stephen Hinck Date: Wed, 5 Aug 2026 15:18:37 -0700 Subject: [PATCH 1/2] feat: PG VACUUM/ANALYZE on every Optimize call - BED-9161 --- drivers/pg/optimize.go | 21 ++------------------ drivers/pg/optimize_test.go | 39 ++----------------------------------- 2 files changed, 4 insertions(+), 56 deletions(-) diff --git a/drivers/pg/optimize.go b/drivers/pg/optimize.go index 8da0cfb8..116c6024 100644 --- a/drivers/pg/optimize.go +++ b/drivers/pg/optimize.go @@ -10,11 +10,6 @@ import ( "github.com/jackc/pgx/v5/pgconn" ) -// deadTupleThreshold is the minimum fraction of dead tuples a partitioned -// parent must accumulate across its partitions before OptimizeStorage will -// vacuum it. -const deadTupleThreshold = 0.1 - // Sum n_dead_tup and n_live_tup across every leaf partition of the parent; const optimizeStorageStatsQuery = ` SELECT @@ -31,8 +26,8 @@ type optimizeStorageConn interface { } func optimizeStorage(ctx context.Context, conn optimizeStorageConn) error { - var targets []string - for _, table := range []string{"node", "edge"} { + targets := []string{"node", "edge"} + for _, table := range targets { var dead, live int64 if err := conn.QueryRow(ctx, optimizeStorageStatsQuery, table).Scan(&dead, &live); err != nil { return fmt.Errorf("query dead tuple stats for %s: %w", table, err) @@ -50,19 +45,7 @@ func optimizeStorage(ctx context.Context, conn optimizeStorageConn) error { slog.Int64("live_tuples", live), slog.Int64("total_tuples", total), slog.Float64("dead_tuple_ratio", deadTupleRatio), - slog.Float64("dead_tuple_threshold", deadTupleThreshold), ) - - if total == 0 { - continue - } - if deadTupleRatio >= deadTupleThreshold { - targets = append(targets, table) - } - } - - if len(targets) == 0 { - return nil } // Targeting the partitioned parents cascades to every partition. diff --git a/drivers/pg/optimize_test.go b/drivers/pg/optimize_test.go index 8fd0f6c5..2fad7bc1 100644 --- a/drivers/pg/optimize_test.go +++ b/drivers/pg/optimize_test.go @@ -11,47 +11,12 @@ import ( ) func TestOptimizeStorage(t *testing.T) { - t.Run("skips vacuum when dead tuple ratios are below threshold", func(t *testing.T) { + t.Run("always vacuums node and edge regardless of dead tuple ratios", func(t *testing.T) { ctx := context.Background() conn := newOptimizeStorageMockConn(t) - expectOptimizeStorageStats(conn, "node", 9, 91) - expectOptimizeStorageStats(conn, "edge", 0, 0) - - require.NoError(t, optimizeStorage(ctx, conn)) - require.NoError(t, conn.ExpectationsWereMet()) - }) - - t.Run("vacuums node only", func(t *testing.T) { - ctx := context.Background() - conn := newOptimizeStorageMockConn(t) - - expectOptimizeStorageStats(conn, "node", 10, 90) + expectOptimizeStorageStats(conn, "node", 0, 0) expectOptimizeStorageStats(conn, "edge", 9, 91) - expectOptimizeStorageVacuum(conn, "VACUUM (ANALYZE) node") - - require.NoError(t, optimizeStorage(ctx, conn)) - require.NoError(t, conn.ExpectationsWereMet()) - }) - - t.Run("vacuums edge only", func(t *testing.T) { - ctx := context.Background() - conn := newOptimizeStorageMockConn(t) - - expectOptimizeStorageStats(conn, "node", 9, 91) - expectOptimizeStorageStats(conn, "edge", 10, 90) - expectOptimizeStorageVacuum(conn, "VACUUM (ANALYZE) edge") - - require.NoError(t, optimizeStorage(ctx, conn)) - require.NoError(t, conn.ExpectationsWereMet()) - }) - - t.Run("vacuums node and edge", func(t *testing.T) { - ctx := context.Background() - conn := newOptimizeStorageMockConn(t) - - expectOptimizeStorageStats(conn, "node", 10, 90) - expectOptimizeStorageStats(conn, "edge", 10, 90) expectOptimizeStorageVacuum(conn, "VACUUM (ANALYZE) node, edge") require.NoError(t, optimizeStorage(ctx, conn)) From 6d438b0d1345f0785d9cc43a608c99d60b38d7c9 Mon Sep 17 00:00:00 2001 From: Stephen Hinck Date: Thu, 6 Aug 2026 08:54:45 -0700 Subject: [PATCH 2/2] remove dead tuple measurement and associated tests --- drivers/pg/optimize.go | 31 ------------------------------- drivers/pg/optimize_test.go | 26 +------------------------- 2 files changed, 1 insertion(+), 56 deletions(-) diff --git a/drivers/pg/optimize.go b/drivers/pg/optimize.go index 116c6024..dc34f5e3 100644 --- a/drivers/pg/optimize.go +++ b/drivers/pg/optimize.go @@ -10,43 +10,12 @@ import ( "github.com/jackc/pgx/v5/pgconn" ) -// Sum n_dead_tup and n_live_tup across every leaf partition of the parent; -const optimizeStorageStatsQuery = ` - SELECT - COALESCE(SUM(stat.n_dead_tup), 0), - COALESCE(SUM(stat.n_live_tup), 0) - FROM pg_partition_tree($1::regclass) tree - LEFT JOIN pg_stat_user_tables stat ON stat.relid = tree.relid - WHERE tree.isleaf -` - type optimizeStorageConn interface { Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) - QueryRow(ctx context.Context, sql string, arguments ...any) pgx.Row } func optimizeStorage(ctx context.Context, conn optimizeStorageConn) error { targets := []string{"node", "edge"} - for _, table := range targets { - var dead, live int64 - if err := conn.QueryRow(ctx, optimizeStorageStatsQuery, table).Scan(&dead, &live); err != nil { - return fmt.Errorf("query dead tuple stats for %s: %w", table, err) - } - - total := dead + live - var deadTupleRatio float64 - if total > 0 { - deadTupleRatio = float64(dead) / float64(total) - } - - slog.InfoContext(ctx, "Queried PostgreSQL table storage statistics", - slog.String("table", table), - slog.Int64("dead_tuples", dead), - slog.Int64("live_tuples", live), - slog.Int64("total_tuples", total), - slog.Float64("dead_tuple_ratio", deadTupleRatio), - ) - } // Targeting the partitioned parents cascades to every partition. stmt := "VACUUM (ANALYZE) " + strings.Join(targets, ", ") diff --git a/drivers/pg/optimize_test.go b/drivers/pg/optimize_test.go index 2fad7bc1..6faf9f36 100644 --- a/drivers/pg/optimize_test.go +++ b/drivers/pg/optimize_test.go @@ -2,7 +2,6 @@ package pg import ( "context" - "errors" "testing" "github.com/jackc/pgx/v5" @@ -11,32 +10,15 @@ import ( ) func TestOptimizeStorage(t *testing.T) { - t.Run("always vacuums node and edge regardless of dead tuple ratios", func(t *testing.T) { + t.Run("always vacuums node and edge", func(t *testing.T) { ctx := context.Background() conn := newOptimizeStorageMockConn(t) - expectOptimizeStorageStats(conn, "node", 0, 0) - expectOptimizeStorageStats(conn, "edge", 9, 91) expectOptimizeStorageVacuum(conn, "VACUUM (ANALYZE) node, edge") require.NoError(t, optimizeStorage(ctx, conn)) require.NoError(t, conn.ExpectationsWereMet()) }) - - t.Run("returns query error", func(t *testing.T) { - ctx := context.Background() - conn := newOptimizeStorageMockConn(t) - expectedErr := errors.New("stats unavailable") - - conn.ExpectQuery(optimizeStorageStatsQuery). - WithArgs("node"). - WillReturnError(expectedErr) - - err := optimizeStorage(ctx, conn) - require.ErrorIs(t, err, expectedErr) - require.ErrorContains(t, err, "query dead tuple stats for node") - require.NoError(t, conn.ExpectationsWereMet()) - }) } func newOptimizeStorageMockConn(t *testing.T) pgxmock.PgxConnIface { @@ -48,12 +30,6 @@ func newOptimizeStorageMockConn(t *testing.T) pgxmock.PgxConnIface { return conn } -func expectOptimizeStorageStats(conn pgxmock.PgxConnIface, table string, dead, live int64) { - conn.ExpectQuery(optimizeStorageStatsQuery). - WithArgs(table). - WillReturnRows(pgxmock.NewRows([]string{"dead", "live"}).AddRow(dead, live)) -} - func expectOptimizeStorageVacuum(conn pgxmock.PgxConnIface, stmt string) { conn.ExpectExec(stmt). WithArgs(pgx.QueryExecModeSimpleProtocol).