neondatabase/neon · error

Unknown RMGR {} for Heap decoding

Error message

Unknown RMGR {} for Heap decoding

What it means

`decode_heapam_record` in wal_decoder tracks visibility-map bit updates for heap records, per PG major version. Within the PG14 branch it only accepts `RM_HEAP_ID` and `RM_HEAP2_ID`; this bail fires when `decoded.xl_rmid` is anything else. Since callers are expected to route only Heap/Heap2 records here, hitting it means a misrouted record or a pg_version that doesn't match the WAL being decoded.

Source

Thrown at libs/wal_decoder/src/decoder.rs:272

                                // the offsets array is omitted if XLOG_HEAP_INIT_PAGE is set
                                0
                            } else {
                                size_of::<u16>() * xlrec.ntuples as usize
                            };
                        assert_eq!(offset_array_len, buf.remaining());

                        if (xlrec.flags & pg_constants::XLH_INSERT_ALL_VISIBLE_CLEARED) != 0 {
                            new_heap_blkno = Some(decoded.blocks[0].blkno);
                        }
                    } else if info == pg_constants::XLOG_HEAP2_LOCK_UPDATED {
                        let xlrec = v14::XlHeapLockUpdated::decode(buf);
                        if (xlrec.flags & pg_constants::XLH_LOCK_ALL_FROZEN_CLEARED) != 0 {
                            old_heap_blkno = Some(decoded.blocks[0].blkno);
                            flags = pg_constants::VISIBILITYMAP_ALL_FROZEN;
                        }
                    }
                } else {
                    anyhow::bail!("Unknown RMGR {} for Heap decoding", decoded.xl_rmid);
                }
            }
            PgMajorVersion::PG15 => {
                if decoded.xl_rmid == pg_constants::RM_HEAP_ID {
                    let info = decoded.xl_info & pg_constants::XLOG_HEAP_OPMASK;

                    if info == pg_constants::XLOG_HEAP_INSERT {
                        let xlrec = v15::XlHeapInsert::decode(buf);
                        assert_eq!(0, buf.remaining());
                        if (xlrec.flags & pg_constants::XLH_INSERT_ALL_VISIBLE_CLEARED) != 0 {
                            new_heap_blkno = Some(decoded.blocks[0].blkno);
                        }
                    } else if info == pg_constants::XLOG_HEAP_DELETE {
                        let xlrec = v15::XlHeapDelete::decode(buf);
                        if (xlrec.flags & pg_constants::XLH_DELETE_ALL_VISIBLE_CLEARED) != 0 {
                            new_heap_blkno = Some(decoded.blocks[0].blkno);
                        }
                    } else if info == pg_constants::XLOG_HEAP_UPDATE

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Fix the dispatch site: only records with xl_rmid RM_HEAP_ID or RM_HEAP2_ID should reach heap decoding
  2. Verify the pg_version passed to the decoder matches the cluster that produced the WAL
  3. If adding a new RMGR, add its own handler branch instead of routing through decode_heapam_record
  4. Log decoded.xl_rmid and xl_info upstream to identify the offending record

Example fix

// before: every record is sent to heap decoding
decode_heapam_record(buf, decoded, pg_version)?;

// after: only Heap/Heap2 records
if matches!(decoded.xl_rmid, pg_constants::RM_HEAP_ID | pg_constants::RM_HEAP2_ID) {
    decode_heapam_record(buf, decoded, pg_version)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if !matches!(decoded.xl_rmid, pg_constants::RM_HEAP_ID | pg_constants::RM_HEAP2_ID) {
    // not a heap record: route to its own handler or skip
    return Ok(None);
}

Type guard

fn is_heap_record(rmid: u8) -> bool {
    matches!(rmid, pg_constants::RM_HEAP_ID | pg_constants::RM_HEAP2_ID)
}

Prevention

When it happens

Trigger: Decoding a PG14 WAL stream where a record with `xl_rmid` other than RM_HEAP_ID/RM_HEAP2_ID is fed into the heap decoder — new dispatch code forwarding all record types, or `pg_version` mismatched with the actual WAL.

Common situations: Extending wal_decoder to new record types and misrouting; an off-by-one version mapping after a PG release; fuzz tests feeding arbitrary rmids into the heap path.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/842f0b720e154e53. Report an issue: GitHub.