neondatabase/neon · error

Unknown WAL record type for Neon RMGR: {}

Error message

Unknown WAL record type for Neon RMGR: {}

What it means

Inside the PG16/PG17 branch of `decode_neonmgr_record`, the record's info byte (masked with XLOG_HEAP_OPMASK) didn't match any known XLOG_NEON_* opcode (INSERT, DELETE, UPDATE/HOT_UPDATE, MULTI_INSERT, LOCK). The wal_decoder in this build simply predates that Neon WAL record type.

Source

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

                                // 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);
                        }
                    }
                    pg_constants::XLOG_NEON_HEAP_LOCK => {
                        let xlrec = v17::rm_neon::XlNeonHeapLock::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;
                        }
                    }
                    info => anyhow::bail!("Unknown WAL record type for Neon RMGR: {}", info),
                }
            }
            PgMajorVersion::PG15 | PgMajorVersion::PG14 => anyhow::bail!(
                "Neon RMGR has no known compatibility with PostgreSQL version {}",
                pg_version
            ),
        }

        if new_heap_blkno.is_some() || old_heap_blkno.is_some() {
            let vm_rel = RelTag {
                forknum: VISIBILITYMAP_FORKNUM,
                spcnode: decoded.blocks[0].rnode_spcnode,
                dbnode: decoded.blocks[0].rnode_dbnode,
                relnode: decoded.blocks[0].rnode_relnode,
            };

            Ok(Some(MetadataRecord::Neonrmgr(NeonrmgrRecord::ClearVmBits(
                ClearVmBits {

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Update wal_decoder / postgres_ffi (and the pageserver stack) to a build that supports the new XLOG_NEON_* opcode
  2. Read the numeric info value from the error message to identify which opcode is missing
  3. If the record type never touches VM bits, add an explicit no-op arm for it and upstream the change
  4. Keep WAL producers and decoder versions in lockstep in the pipeline

Example fix

// before: any unknown Neon opcode aborts decoding
info => anyhow::bail!("Unknown WAL record type for Neon RMGR: {}", info),

// after: explicitly ignore record types that never clear VM bits
info if !affects_visibility_map(info) => { /* no VM bookkeeping needed */ }
info => anyhow::bail!("Unknown WAL record type for Neon RMGR: {}", info),
Defensive patterns

Strategy: try-catch

Try / catch

match decode_neonmgr_record(buf, decoded, pg_version) {
    Ok(rec) => { /* ... */ }
    Err(e) if e.to_string().contains("Unknown WAL record type for Neon RMGR") => {
        // decoder older than the WAL producer: stop ingesting and upgrade
        return Err(e.context("wal_decoder too old for this WAL stream; upgrade wal_decoder/postgres_ffi"));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Decoding WAL produced by a newer Neon postgres build that emits a new Neon-RMGR record opcode, while the wal_decoder/postgres_ffi in this build doesn't know it yet.

Common situations: Pageserver/wal_decoder older than the Neon postgres build writing WAL; ingesting WAL from a canary Neon release; experimental Neon forks adding new Neon record types.

Related errors


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