Module: Pgbus::TableMaintenance

Defined in:
lib/pgbus/table_maintenance.rb

Overview

Proactive table maintenance to reduce bloat on PGMQ queue tables.

PGMQ's read operation UPDATEs three columns (vt, read_ct, last_read_at) on every message read. With the default fillfactor of 100, every UPDATE creates a new heap tuple AND a new index entry — the dead tuple and its old index pointer remain until VACUUM. Under sustained load, autovacuum can't keep up and B-tree indexes bloat.

Setting fillfactor=70 on queue tables reserves 30% of each page for update churn. Because vt is indexed and changes on every read, these writes are not HOT updates, but leaving headroom on heap pages still reduces page density for a table that is updated heavily between vacuum passes.

More importantly, this module provides targeted VACUUM: instead of relying solely on autovacuum's global heuristics, the dispatcher periodically checks pg_stat_user_tables for tables with high dead tuple ratios and vacuums them explicitly. This is inspired by pgque's philosophy of measuring bloat before acting.

Constant Summary collapse

FILLFACTOR =
70
BLOAT_THRESHOLD =
0.1
MAINTENANCE_INTERVAL =

6 hours

6 * 3600

Class Method Summary collapse

Class Method Details

.fillfactor_sql_for_all_queuesObject



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/pgbus/table_maintenance.rb', line 33

def fillfactor_sql_for_all_queues
  <<~SQL
    DO $$
    DECLARE
      q RECORD;
    BEGIN
      FOR q IN SELECT queue_name FROM pgmq.meta LOOP
        EXECUTE format('ALTER TABLE pgmq.q_%I SET (fillfactor = #{FILLFACTOR})', q.queue_name);
      END LOOP;
    END $$;
  SQL
end

.fillfactor_sql_for_queue(queue_name) ⇒ Object



29
30
31
# File 'lib/pgbus/table_maintenance.rb', line 29

def fillfactor_sql_for_queue(queue_name)
  "ALTER TABLE pgmq.q_#{queue_name} SET (fillfactor = #{FILLFACTOR});"
end

.reindex_sql(table) ⇒ Object



78
79
80
81
# File 'lib/pgbus/table_maintenance.rb', line 78

def reindex_sql(table)
  schema, relname = table.split(".", 2)
  "REINDEX TABLE CONCURRENTLY \"#{schema}\".\"#{relname}\""
end

.run_maintenance(conn, threshold: BLOAT_THRESHOLD, reindex: true) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/pgbus/table_maintenance.rb', line 83

def run_maintenance(conn, threshold: BLOAT_THRESHOLD, reindex: true)
  candidates = vacuum_candidates(conn, threshold: threshold)
  return 0 if candidates.empty?

  maintained = 0
  candidates.each do |candidate|
    table = candidate[:table]
    Pgbus.logger.info do
      "[Pgbus::TableMaintenance] Vacuuming #{table} " \
        "(dead_ratio=#{candidate[:dead_ratio]}, dead=#{candidate[:dead_tuples]})"
    end
    conn.exec(vacuum_sql(table))

    if reindex
      Pgbus.logger.info { "[Pgbus::TableMaintenance] Reindexing #{table}" }
      conn.exec(reindex_sql(table))
    end

    maintained += 1
  rescue StandardError => e
    Pgbus.logger.error { "[Pgbus::TableMaintenance] Failed to maintain #{table}: #{e.message}" }
  end

  maintained
end

.vacuum_candidates(conn, threshold: BLOAT_THRESHOLD) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/pgbus/table_maintenance.rb', line 46

def vacuum_candidates(conn, threshold: BLOAT_THRESHOLD)
  rows = conn.exec(<<~SQL)
    SELECT schemaname, relname, n_dead_tup, n_live_tup
    FROM pg_stat_user_tables
    WHERE schemaname = 'pgmq'
      AND relname LIKE 'q_%'
    ORDER BY n_dead_tup DESC
  SQL

  rows.each_with_object([]) do |row, candidates|
    dead = row["n_dead_tup"].to_i
    live = row["n_live_tup"].to_i
    total = dead + live
    next if total.zero?

    ratio = dead.to_f / total
    next unless ratio > threshold

    candidates << {
      table: "#{row["schemaname"]}.#{row["relname"]}",
      dead_tuples: dead,
      live_tuples: live,
      dead_ratio: ratio.round(4)
    }
  end
end

.vacuum_sql(table) ⇒ Object



73
74
75
76
# File 'lib/pgbus/table_maintenance.rb', line 73

def vacuum_sql(table)
  schema, relname = table.split(".", 2)
  "VACUUM \"#{schema}\".\"#{relname}\""
end