Postgresql源码(158)pg_filenode.map文件作用relmap和redo流程(RM_RELMAP_ID = 7)

pg_filenode.map文件作用

global和base下每个db都会带一个pg_filenode.map文件,用来保存基础系统表 【oid→文件名】的映射关系。因为普通表可以通过pg_class的relfilenode找到表文件,但是启动时pg_class显然无法通过查找自己来找到表文件。还有一些共享系统表,例如pg_authid,如果在一个库中执行了vacuum full,auth表的relfilenode变了,就需要更新所有库中的pg_class,这里需要跨库并且还要在一个事务内,现在实现做不到这一点,所以基础表的 【oid→文件名】映射关系,是用文件来保存的,并且会写WAL日志保证持久化和一致性。

relmapper.c 给出了解释:

For most tables, the physical file underlying the table is specified by

pg_class.relfilenode. However, that obviously won't work for pg_class

itself, nor for the other "nailed" catalogs for which we have to be able

to set up working Relation entries without access to pg_class. It also

does not work for shared catalogs, since there is no practical way to

update other databases' pg_class entries when relocating a shared catalog.

Therefore, for these special catalogs (henceforth referred to as "mapped

catalogs") we rely on a separately maintained file that shows the mapping

from catalog OIDs to filenumbers.

查看哪些表是mapped的

复制代码
SELECT relname, relfilenode FROM pg_class WHERE relfilenode = 0 ORDER BY relname;

pg_filenode.map文件读取

文件结构比较简单,可以用shell做查询:

复制代码
#define RELMAPPER_FILEMAGIC  0x592717
#define MAX_MAPPINGS         64

typedef struct RelMapping {
    Oid            mapoid;          /* 4 bytes: 系统表 OID */
    RelFileNumber  mapfilenumber;   /* 4 bytes: 对应的 filenumber */
} RelMapping;                       /* 每条 8 bytes */

typedef struct RelMapFile {
    int32       magic;              /* 4 bytes: 固定值 0x592717 */
    int32       num_mappings;       /* 4 bytes: 有效映射条数 */
    RelMapping  mappings[64];       /* 64 × 8 = 512 bytes (固定大小) */
    pg_crc32c   crc;                /* 4 bytes: CRC32C 校验 */
} RelMapFile;                       /* 总计: 4+4+512+4 = 524 bytes */

脚本

复制代码
MAP_FILE="${1:-$PGDATA/global/pg_filenode.map}"

# ========== 读 magic number (前4字节, little-endian) 
#   -A n    : Address radix = none,不显示左侧的地址偏移量
#   -t x4   : Type = hex, 4 bytes 一组,即以 4 字节十六进制整数输出
#   -N 4    : 只读取 4 个字节
#   -j 0    : Jump,跳过前 0 个字节(即从文件开头开始读)
# tr -d ' ' : 删除输出中的空格
magic=$(od -An -tx4 -N4 -j0 "$MAP_FILE" | tr -d ' ')
echo "Magic: 0x$magic"

# ========== 读 num_mappings (第5-8字节) 
#   -t d4   : Type = decimal, 4 bytes 一组,即以 4 字节有符号十进制整数输出
#   -N 4    : 只读取 4 个字节
#   -j 4    : Jump 4,跳过前 4 个字节(跳过 magic),从第 5 字节开始读
num=$(od -An -td4 -N4 -j4 "$MAP_FILE" | tr -d ' ')
echo "Mappings count: $num"
echo "-----------------------------------"
# printf 格式化输出:%-12s 表示左对齐、占 12 字符宽度的字符串
printf "%-12s -> %s\n" "OID" "FileNumber"
echo "-----------------------------------"

# ========== 逐条读取 mappings ===
# 每条 mapping = 8 bytes (oid 4字节 + filenumber 4字节)
# 数据从文件的第 8 字节(offset=8)开始,前面是 magic(4) + num(4)
for ((i=0; i<num; i++)); do
    # 计算当前第 i 条 mapping 的文件偏移量
    # offset = 8 (header大小) + i * 8 (每条8字节)
    offset=$((8 + i * 8))

    # 读 OID:从 offset 处读 4 字节,输出为十进制
    #   -j$offset : 跳到当前 mapping 的起始位置
    oid=$(od -An -td4 -N4 -j$offset "$MAP_FILE" | tr -d ' ')

    # 读 FileNumber:从 offset+4 处读 4 字节(OID后面紧跟的4字节)
    fnum=$(od -An -td4 -N4 -j$((offset+4)) "$MAP_FILE" | tr -d ' ')

    # printf 格式化输出:%-12d 表示左对齐、占12字符宽的十进制整数
    printf "%-12d -> %d\n" "$oid" "$fnum"
done

redo流程

关键数据结构

c 复制代码
/* src/include/utils/relmapper.h */

/*
 * xl_relmap_update: 关系映射更新的 WAL 记录数据
 * 包含目标数据库信息和完整的新映射文件内容(整体替换)
 */
typedef struct xl_relmap_update
{
    Oid         dbid;       /* 数据库 ID; 0 表示共享映射(shared catalog) */
    Oid         tsid;       /* 数据库所在的表空间; 或 pg_global */
    int32       nbytes;     /* 映射数据的字节数(= sizeof(RelMapFile)) */
    char        data[FLEXIBLE_ARRAY_MEMBER];  /* 变长: 完整的 RelMapFile 内容 */
} xl_relmap_update;

#define MinSizeOfRelmapUpdate offsetof(xl_relmap_update, data)

代码

c 复制代码
/* src/backend/utils/cache/relmapper.c:1096-1142 */

/*
 * RELMAP 资源管理器的 redo 入口函数
 */
void
relmap_redo(XLogReaderState *record)
{
    uint8       info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

    /* relmap 记录不使用 backup block */
    Assert(!XLogRecHasAnyBlockRefs(record));

    if (info == XLOG_RELMAP_UPDATE)
    {
        /*
         * ===== 处理 RELMAP_UPDATE: 重写映射文件 =====
         */
        xl_relmap_update *xlrec = (xl_relmap_update *) XLogRecGetData(record);
        RelMapFile  newmap;
        char       *dbpath;

        /*
         * 安全性检查: WAL 中的 nbytes 必须等于 sizeof(RelMapFile)
         * 如果不匹配, 说明 WAL 记录损坏, 必须 PANIC
         */
        if (xlrec->nbytes != sizeof(RelMapFile))
            elog(PANIC, "relmap_redo: wrong size %u in relmap update record",
                 xlrec->nbytes);

        /* 从 WAL 记录的 data 区域复制出完整的新映射文件内容 */
        memcpy(&newmap, xlrec->data, sizeof(newmap));

        /*
         * 构建目标数据库的路径
         * 例如: base/12345 (对于普通数据库)
         *       global     (对于共享系统表, dbid=0)
         */
        dbpath = GetDatabasePath(xlrec->dbid, xlrec->tsid);

        /*
         * 将新映射写入磁盘并发送 sinval 消息:
         * - 第2参数 false: 不写新 WAL(因为我们正在回放 WAL)
         * - 第3参数 true:  发送 sinval 失效消息(通知其他后端刷新缓存)
         * - 第4参数 false: 不保留文件(非事务上下文)
         *
         * 注意: 对于 CREATE DATABASE 场景, 发送 sinval 消息是不必要但无害的。
         * 如果想避免, 可以在 WAL 记录中加一个标志位区分两种场景,
         * 但当前实现选择了简洁性。
         */
        LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE);
        write_relmap_file(&newmap, false, true, false,
                          xlrec->dbid, xlrec->tsid, dbpath);
        LWLockRelease(RelationMappingLock);

        pfree(dbpath);
    }
    else
        elog(PANIC, "relmap_redo: unknown op code %u", info);
}

gdb分析redo

主库执行vacuum full pg_authid;,oid是1260。

备库redo时把完整的relmap更新到文件中了:

相关推荐
秦jh_2 小时前
【Redis】初识高并发分布式和Redis
数据库·redis·缓存
远方16092 小时前
115-使用freesql体验Oracle 多版本特性
大数据·数据库·sql·ai·oracle·database
happymaker06262 小时前
JDBC(MySQL)——DAY01
数据库·mysql
qqacj2 小时前
MSSQL2022的一个错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序
数据库·microsoft
ren049182 小时前
MySQL
数据库·mysql
良逍Ai出海2 小时前
OpenClaw 新手最该先搞懂的 2 套命令
android·java·数据库
Keanu-2 小时前
Redis 主从复制及哨兵模式配置
服务器·数据库·redis
blues92573 小时前
【JOIN】关键字在MySql中的详细使用
数据库·mysql
x-cmd3 小时前
[260307] x-cmd v0.8.6:新增 gpt-5.4 模型支持,sudo/os/hostname/cpu 等模块文档更新
java·数据库·gpt·sudo·x-cmd·googel