How to detect chained and migrated rows in PostgreSQL?

How do you think you could detect chained and migrated rows in PostgreSQL?


Chained and migrated rows occur in PostgreSQL when rows are updated and cannot fit in their original block due to space constraints, leading to performance degradation. The scripts provided can help identify and analyze these scenarios indirectly by inspecting certain statistics, such as live tuples, dead tuples, hot updates, and block-level statistics. Here's how to use the provided SQL scripts to detect and analyze chained and migrated rows:

1. Understand Key Metrics

  • n_live_tup: Number of live tuples (active rows in a table).
  • n_dead_tup: Number of dead tuples (deleted or updated rows awaiting cleanup by VACUUM).
  • n_tup_hot_upd: Number of tuples updated using HOT (Heap-Only Tuple), which avoids creating new index entries.
  • n_tup_ins: Number of tuples inserted.
  • n_tup_upd: Number of tuples updated.
  • n_tup_del: Number of tuples deleted.
For chained/migrated rows:
  • A high n_dead_tup relative to n_live_tup suggests dead tuples, possibly due to migration.
  • A high n_tup_upd but low n_tup_hot_upd may indicate updates resulting in row migrations rather than HOT updates.

2. Monitoring IOPS distribution

Query 1: Analyze Overall Table Statistics

SELECT relname,
       n_live_tup,
       n_dead_tup,
       n_tup_hot_upd,
       n_tup_ins,
       n_tup_upd,
       n_tup_del,
       n_tup_hot_upd + n_tup_ins + n_tup_upd + n_tup_del AS total_rows
FROM pg_stat_all_tables;
Purpose:
  • Use this query to get a broad overview of the tables in the database, including live tuples, dead tuples, and HOT updates.
  • High n_dead_tup relative to n_live_tup may indicate row migrations or poor vacuum performance.
Steps:
  1. Run the query on the database.
  2. Identify tables with:
    • High n_dead_tup (potential dead tuples from migrations).
    • Low n_tup_hot_upd compared to n_tup_upd (indicating updates are creating new index entries instead of HOT updates).

Query 2: Focus on User Tables

SELECT
    relname AS tablename,
    n_live_tup,
    n_dead_tup,
    n_tup_hot_upd,
    n_tup_ins,
    n_tup_upd,
    n_tup_del,
    (n_tup_hot_upd + n_tup_ins + n_tup_upd + n_tup_del) AS total_rows
FROM
    pg_stat_user_tables;
Purpose:
  • Similar to Query 1 but focuses only on user-defined tables (ignores system catalogs).
Steps:
  1. Run the query and analyze the same metrics as above.
  2. Prioritize tables with:
    • High n_dead_tup.
    • Low n_tup_hot_upd.

Query 3: Analyze Block-Level Access Patterns

SELECT
    psut.relname,
    psat.heap_blks_hit,
    psat.heap_blks_read,
    psat.idx_blks_hit,
    psat.idx_blks_read,
    psut.idx_scan,
    psut.n_tup_ins,
    psut.n_tup_upd,
    psut.n_tup_del,
    psut.n_tup_hot_upd,
    (psat.heap_blks_hit + psat.heap_blks_read) AS total_blocks,
    (psat.idx_blks_hit + psat.idx_blks_read) AS total_index_blocks,
    (psat.heap_blks_hit + psat.heap_blks_read + psat.idx_blks_hit + psat.idx_blks_read) AS total_blocks_read,
    (psut.idx_scan + psat.heap_blks_hit + psat.heap_blks_read + psat.idx_blks_hit + psat.idx_blks_read) AS total_access
FROM
    pg_stat_user_tables psut
JOIN
    pg_statio_user_tables psat
ON
    psut.relid = psat.relid;
Purpose:
  • This query combines logical table access statistics (e.g., updates, deletions, HOT updates) with block-level I/O statistics.
Steps:
  1. Focus on these metrics:
    • High heap_blks_read relative to heap_blks_hit: Indicates high disk I/O due to row migrations.
    • High n_tup_upd but low n_tup_hot_upd: Suggests updates causing migrations.
    • High idx_blks_read: Indicates index scans caused by row migrations.
  2. Investigate tables with high read activity or large differences between block hits and reads.

3. Interpretation and Action

3.1 Indicators of Chained/Migrated Rows

  • High n_dead_tup: Suggests dead tuples left behind due to updates or deletes. This can result from row migrations.
  • Low n_tup_hot_upd: Indicates updates are creating new tuples (migrations) rather than reusing space via HOT.
  • High heap_blks_read: Excessive disk I/O due to fetching migrated rows.

3.2 Actions to Mitigate

  1. VACUUM and Autovacuum:
    • Ensure regular VACUUM or VACUUM ANALYZE is run on the affected tables to clean up dead tuples:
VACUUM ANALYZE table_name;
  1. Rebuild Tables or Indexes:
    • If excessive row migrations occur, consider CLUSTER or VACUUM FULL to reorganize the table and reclaim space:
VACUUM FULL table_name;
REINDEX TABLE table_name;
  1. Analyze Table Design:
    • Check if FILLFACTOR for the table can be adjusted to leave more free space per block, reducing row migrations:
ALTER TABLE table_name SET (FILLFACTOR = 70);
  1. Review Application Logic:
    • Identify patterns causing frequent updates and optimize application design or queries.

4. Automation

  • Automate the execution of these queries and report tables with high n_dead_tup or low n_tup_hot_upd using a monitoring tool like pgAdmin, Prometheus, or custom scripts.
By using the provided scripts and following the recommended steps, you can detect and address chained/migrated rows effectively, improving PostgreSQL performance.
Comparing Storage Mechanisms: Oracle’s PCTFREE, PCTUSED, and Row Migration vs. PostgreSQL’s MVCC and Tuple Management
What are the possible scenarios for deadlock in PostgreSQL?
How to monitor CPU Cache Buffers Chains Latch in PostgreSQL?
About Shiv Iyer 500 Articles
Open Source Database Systems Engineer with a deep understanding of Optimizer Internals, Performance Engineering, Scalability and Data SRE. Shiv currently is the Founder, Investor, Board Member and CEO of multiple Database Systems Infrastructure Operations companies in the Transaction Processing Computing and ColumnStores ecosystem. He is also a frequent speaker in open source software conferences globally.